Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>CfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
// n.getParent() isn't null. This just the case where n is the root
// node that gatherCb started at.
if (parent == null) {
return;
}
// Make sure that the name node is purely a read.
if ((NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| parent.isVar() || parent.isInc() || parent.isDec() ||
parent.isParamList() || parent.isCatch()) {
return;
}
String name = n.getString();
if (compiler.getCodingConvention().isExported(name)) {
return;
}
Definition def = reachingDef.getDef(name, cfgNode);
// TODO(nicksantos): We need to add some notion of @const outer
// scope vars. We can inline those just fine.
if (def != null &&
!reachingDef.dependsOnOuterScopeVars(def)) {
candidates.add(new Candidate(name, def, n, cfgNode));
}
}
}
};
NodeTraversal.traverse(compiler, cfgNode, gatherCb);
}
}
/**
* Models the connection between a definition and a use of that definition.
*/
private class Candidate {
// Name of the variable.
private final String varName;
// Nodes related to the definition.
private Node def;
getDefinition(getDefCfgNode(), null);
getNumUseInUseCfgNode(useCfgNode, null);
// Definition was not found.
if (def == null) {
return false;
}
// Check that the assignment isn't used as a R-Value.
// TODO(user): Certain cases we can still inline.
if (def.isAssign() && !NodeUtil.isExprAssign(def.getParent())) {
return false;
}
// The right of the definition has side effect:
// Example, for x:
// x = readProp(b), modifyProp(b); print(x);
if (checkRightOf(def, getDefCfgNode(), SIDE_
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>BetweenNodes<Node, ControlFlowGraph.Branch>(
cfg,
cfg.getDirectedGraphNode(getDefCfgNode()),
cfg.getDirectedGraphNode(useCfgNode),
SIDE_EFFECT_PREDICATE,
Predicates.
<DiGraphEdge<Node, ControlFlowGraph.Branch>>alwaysTrue(),
false);
if (pathCheck.somePathsSatisfyPredicate()) {
return false;
}
}
return true;
}
/**
* Actual transformation.
*/
private void inlineVariable() {
Node defParent = def.getParent();
Node useParent = use.getParent();
if (def.isAssign()) {
Node rhs = def.getLastChild();
rhs.detachFromParent();
// Oh yes! I have grandparent to remove this.
Preconditions.checkState(defParent.isExprResult());
while (defParent.getParent().isLabel()) {
defParent = defParent.getParent();
}
defParent.detachFromParent();
useParent.replaceChild(use, rhs);
} else if (defParent.isVar()) {
Node rhs = def.getLastChild();
def.removeChild(rhs);
useParent.replaceChild(use, rhs);
} else {
Preconditions.checkState(false, "No other definitions can be inlined.");
}
compiler.reportCodeChange();
}
/**
* Set the def node
*
* @param n A node that has a corresponding CFG node in the CFG.
*/
private void getDefinition(Node n, Node parent) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (n.getString().equals(varName) && n.hasChildren()) {
def = n;
}
return;
case Token.ASSIGN:
Node lhs = n.getFirstChild();
if (lhs.isName() && lhs.getString().equals(varName)) {
def = n;
}
return;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
/**
* Computes the number of uses of the variable varName and store it in
* numUseWithinUse
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>CfgNode.
*/
private void getNumUseInUseCfgNode(Node n, Node parant) {
AbstractCfgNodeTraversalCallback gatherCb =
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName() && n.getString().equals(varName) &&
// do not count in if it is left child of an assignment operator
!(parent.isAssign() &&
(parent.getFirstChild() == n))) {
numUseWithinUseCfgNode++;
}
}
};
NodeTraversal.traverse(compiler, n, gatherCb);
}
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the right of n.
*
* Example:
*
* NotChecked(), NotChecked(), n, Checked(), Checked();
*/
private static boolean checkRightOf(
Node n, Node expressionRoot, Predicate<Node> predicate) {
for (Node p = n; p != expressionRoot; p = p.getParent()) {
for (Node cur = p.getNext(); cur != null; cur = cur.getNext()) {
if (predicate.apply(cur)) {
return true;
}
}
}
return false;
}
/**
* Given an expression by its root and sub-expression n, return true if there
* the predicate is true for some expression on the left of n.
*
* Example:
*
* Checked(), Checked(), n, NotChecked(), NotChecked();
*/
private static boolean checkLeftOf(
Node n, Node expressionRoot, Predicate<Node> predicate) {
for (Node p = n.getParent(); p != expressionRoot; p = p.getParent()) {
for (Node cur = p.getParent().getFirstChild(); cur != p;
cur = cur.getNext()) {
if (predicate.apply(cur)) {
return true;
}
}
}
return false;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.isVar() &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var =
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
}
case Token.INC:
case Token.DEC: {
Node lhs = n.getFirstChild();
if (lhs.isName()) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
reportError(t, n, name);
}
}
break;
}
}
}
/**
* Gets whether a variable is a constant initialized to a literal value at
* the point where it is declared.
*/
private boolean isConstant(Scope.Var var) {
return var != null && var.isConst();
}
/**
* Reports a reassigned constant error.
*/
void reportError(NodeTraversal t, Node n, String name) {
compiler.report(t.makeError(n, CONST_REASSIGNED_VALUE_ERROR, name));
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>CleaupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new FieldCleanupPass(compiler);
}
};
final PassFactory scopeCleanupPassFactory =
new HotSwapPassFactory("ScopeCleanupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new MemoizedScopeCleanupPass(compiler);
}
};
final PassFactory globalVarRefCleanupPassFactory =
new HotSwapPassFactory("GlobalVarRefCleanupPassFactory", false) {
@Override
protected HotSwapCompilerPass createInternal(
AbstractCompiler compiler) {
return new GlobalVarRefCleanupPass(compiler);
}
};
/**
* A CleanupPass implementation that will remove stored scopes from the
* MemoizedScopeCreator of the compiler instance for a the hot swapped script.
* <p>
* This pass will also clear out Source Nodes of Function Types declared on
* Vars tracked by MemoizedScopeCreator
*/
static class MemoizedScopeCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public MemoizedScopeCleanupPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
ScopeCreator creator = compiler.getTypedScopeCreator();
if (creator instanceof MemoizedScopeCreator) {
MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator;
String newSrc = scriptRoot.getSourceFileName();
for (Var var : scopeCreator.getAllSymbols()) {
JSType type = var.getType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null
&& newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) {
fnType.setSource(null);
}
}
}
scopeCreator.removeScopesForScript(originalRoot.getSourceFileName());
}
}
@Override
public void process(Node externs, Node root) {
// MemoizedScopeCleanupPass should not do work during process.
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>getSingletonGetterClassName(callNode);
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
nextConvention.applySingletonGetter(
functionType, getterType, objectType);
}
@Override
public boolean isInlinableFunction(Node n) {
return nextConvention.isInlinableFunction(n);
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return nextConvention.getDelegateRelationship(callNode);
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
nextConvention.applyDelegateRelationship(
delegateSuperclass, delegateBase, delegator,
delegateProxy, findDelegate);
}
@Override
public String getDelegateSuperclassName() {
return nextConvention.getDelegateSuperclassName();
}
@Override
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
nextConvention.checkForCallingConventionDefiningCalls(
n, delegateCallingConventions);
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
nextConvention.defineDelegateProxyPrototypeProperties(
registry, scope, delegateProxyPrototypes, delegateCallingConventions);
}
@Override
public String getGlobalObject() {
return nextConvention.getGlobalObject();
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return nextConvention.getAssertionFunctions();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
return nextConvention.describeFunctionBind(n, useTypeInfo);
}
@Override
public boolean isPropertyTestFunction(Node call) {
return nextConvention.isPropertyTestFunction(call);
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return next
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> @Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
// do nothing
}
@Override
public String getAbstractMethodName() {
return null;
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return null;
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
// do nothing.
}
@Override
public boolean isInlinableFunction(Node n) {
Preconditions.checkState(n.isFunction());
return true;
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return null;
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
// do nothing.
}
@Override
public String getDelegateSuperclassName() {
return null;
}
@Override
public void checkForCallingConventionDefiningCalls(Node n,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public String getGlobalObject() {
return "window";
}
@Override
public boolean isPropertyTestFunction(Node call) {
return false;
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return null;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return Collections.emptySet();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
if (!n.isCall()) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.equals("Function.prototype.bind.call")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safeNext(thisValue);
return new Bind(fn, thisValue, parameters);
}
}
if (callTarget.isGetProp()
&& callTarget.getLastChild().getString().equals("bind")) {
Node maybeFn = callTarget.getFirstChild();
JSType maybeFnType = maybeFn.getJSType();
FunctionType fnType = null;
if (useTypeInfo && maybeFnType != null) {
fnType = maybeFnType.restrictByNotNullOrUndefined()
.toMaybeFunctionType();
}
if (fnType != null || maybeFn.isFunction()) {
// (function(){}).bind(self, args...);
Node thisValue = callTarget.getNext();
Node parameters = safeNext(thisValue);
return new Bind(maybeFn, thisValue, parameters);
}
}
return null;
}
@Override
public Collection<String> getIndirectlyDeclaredProperties() {
return ImmutableList.of();
}
private Node safeNext(Node n) {
if (n != null) {
return n.getNext();
}
return null;
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>isSimpleOp) {
msg = "The result of the '" + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCodeChange();
}
}
private void addExtern() {
Node name = IR.name(PROTECTOR_FN);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node var = IR.var(name);
// Add "@noalias" so we can strip the method when AliasExternals is enabled.
JSDocInfoBuilder builder = new JSDocInfoBuilder(false);
builder.recordNoAlias();
var.setJSDocInfo(builder.build(var));
CompilerInput input = compiler.getSynthesizedExternsInput();
input.getAstRoot(compiler).addChildrenToBack(var);
compiler.reportCodeChange();
}
/**
* Remove side-effect sync functions.
*/
static class StripProtection extends AbstractPostOrderCallback implements CompilerPass {
private final AbstractCompiler compiler;
StripProtection(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isCall()) {
Node target = n.getFirstChild();
// TODO(johnlenz): add this to the coding convention
// so we can remove goog.reflect.sinkValue as well.
if (target.isName() && target.getString().equals(PROTECTOR_FN)) {
Node expr = n.getLastChild();
n.detachChildren();
parent.replaceChild(n, expr);
}
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> the $ must be
* upper case.
* $A Constant - doesn't have to be anything in front of the $
* </pre>
*/
@Override
public boolean isConstant(String name) {
if (name.length() <= 1) {
return false;
}
// In compiled code, '$' is often a namespace delimiter. To allow inlining
// of namespaced constants, we strip off any namespaces here.
int pos = name.lastIndexOf('$');
if (pos >= 0) {
name = name.substring(pos + 1);
if (name.length() == 0) {
return false;
}
}
return isConstantKey(name);
}
@Override
public boolean isConstantKey(String name) {
if (name.isEmpty() || !Character.isUpperCase(name.charAt(0))) {
return false;
}
// hack way of checking that there aren't any lower-case letters
return name.toUpperCase().equals(name);
}
/**
* {@inheritDoc}
*
* <p>This enforces Google's convention about enum key names. They must match
* the regular expression {@code [A-Z0-9][A-Z0-9_]*}.
*
* <p>Examples:
* <ul>
* <li>A</li>
* <li>213</li>
* <li>FOO_BAR</li>
* </ul>
*/
@Override
public boolean isValidEnumKey(String key) {
return ENUM_KEY_PATTERN.matcher(key).matches();
}
/**
* {@inheritDoc}
*
* <p>In Google code, parameter names beginning with {@code opt_} are
* treated as optional arguments.
*/
@Override
public boolean isOptionalParameter(Node parameter) {
return parameter.getString().startsWith(OPTIONAL_ARG_PREFIX);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return VAR_ARGS_NAME.equals(parameter.getString());
}
/**
* {@inheritDoc}
*
* <p>In Google code, any global name starting with an underscore is
* considered exported.
*/
@Override
public boolean isExported(String name, boolean local) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
LanguageMode languageMode,
boolean acceptConstKeyword) {
return createConfig(isIdeMode, languageMode, acceptConstKeyword, null);
}
public static Config createConfig(boolean isIdeMode,
LanguageMode languageMode,
boolean acceptConstKeyword,
Set<String> extraAnnotationNames) {
initResourceConfig();
Set<String> effectiveAnnotationNames;
if (extraAnnotationNames == null) {
effectiveAnnotationNames = annotationNames;
} else {
effectiveAnnotationNames = new HashSet<String>(annotationNames);
effectiveAnnotationNames.addAll(extraAnnotationNames);
}
return new Config(effectiveAnnotationNames, suppressionNames,
isIdeMode, languageMode, acceptConstKeyword);
}
public static Set<String> getReservedVars() {
initResourceConfig();
return reservedVars;
}
private static synchronized void initResourceConfig() {
if (annotationNames != null) {
return;
}
ResourceBundle config = ResourceBundle.getBundle(configResource);
annotationNames = extractList(config.getString("jsdoc.annotations"));
suppressionNames = extractList(config.getString("jsdoc.suppressions"));
reservedVars = extractList(config.getString("compiler.reserved.vars"));
}
private static Set<String> extractList(String configProp) {
String[] names = configProp.split(",");
Set<String> trimmedNames = Sets.newHashSet();
for (String name : names) {
trimmedNames.add(name.trim());
}
return ImmutableSet.copyOf(trimmedNames);
}
/**
* Parses the JavaScript text given by a reader.
*
* @param sourceString Source code from the file.
* @param errorReporter An error.
* @param logger A logger.
* @return The AST of the given text.
* @throws IOException
*/
public static ParseResult parse(StaticSourceFile sourceFile,
String sourceString,
Config config,
ErrorReporter errorReporter,
Logger logger) throws IOException {
Context cx = Context.enter();
cx.setErrorReporter(errorReporter);
cx.setLanguageVersion(Context.VERSION_1_5);
CompilerEnvirons compilerEnv = new CompilerEnvirons();
compilerEnv.initFromContext(cx);
compilerEnv.setRecordingComments(true);
compilerEnv.set
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>State(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between JsDoc on the object literal node and JsDoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> t.makeError(n, MISSING_EXTENDS_TAG_WARNING, subObject.toString())));
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* must have some relationship with the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType castType, JSType type) {
if (!type.canCastTo(castType)) {
registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST,
type.toString(), castType.toString())));
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
* @return The variable we end up with. Most of the time, this will just
* be {@code var}, but in some rare cases we will need to declare
* a new var with new source info.
*/
Var expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var var, String variableName, JSType newType) {
Var newVar = var;
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n, parent)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type. We should redeclare it at the new input site.
if (var.input == null) {
Scope s = var.getScope();
s.undeclare(var);
newVar = s.declare(variableName, n, varType, input, false);
n.setJSType(varType);
if (parent.isVar()) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.isFunction());
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().isExprResult()) ||
!newType.isEquivalentTo(varType)) {
report(JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
return newVar;
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented and correctly typed.
*/
void expectAllInterfaceProperties(NodeTraversal t, Node n,
FunctionType type) {
ObjectType instance = type
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>prototype.bar".
*
* @param n The node.
* @param dereference If true, the type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was originally defined.
if (n.isGetProp()) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
if (objectType.getConstructor() != null &&
objectType.getConstructor().isInterface()) {
objectType = FunctionType.getTopDefiningInterface(
objectType, propName);
} else {
// classes
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(type.toObjectType() != null &&
type.toObjectType().getConstructor() != null)) {
return type.toString();
} else if (qualifiedName != null) {
return qualifiedName;
} else if (type.isFunctionType()) {
// Don't show complex function names.
return "function";
} else {
return type.toString();
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.isTypeOf() && right.isString()) {
typeOfNode = left;
stringNode = right;
} else if (right.isTypeOf() &&
left.isString()) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().isString()) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {
ungetChar(c2);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return JsDocToken.STRING;
} else {
do {
c1 = c2;
c2 = getChar();
if (c1 == '.' && c2 == '<') {
ungetChar(c2);
ungetChar(c1);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return JsDocToken.STRING;
} else {
if (isJSDocString(c2)) {
addToString(c1);
} else {
ungetChar(c2);
addToString(c1);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return JsDocToken.STRING;
}
}
} while (true);
}
}
}
}
}
/**
* Gets the remaining JSDoc line without the {@link JsDocToken#EOL},
* {@link JsDocToken#EOF} or {@link JsDocToken#EOC}.
*/
@SuppressWarnings("fallthrough")
String getRemainingJSDocLine() {
int c;
for (;;) {
c = getChar();
switch (c) {
case '*':
if (peekChar() != '/') {
addToString(c);
break;
}
// fall through
case EOF_CHAR:
case '\n':
ungetChar(c);
this.string = getStringFromBuffer();
stringBufferTop = 0;
return this.string;
default:
addToString(c);
break;
}
}
}
final int getLineno() { return lineno; }
final int getCharno() {
return lineno == initLineno? initCharno + charno : charno;
}
final String getString() { return string; }
final boolean eof() { return hitEOF; }
private String getStringFromBuffer() {
tokenEnd = cursor;
return new String(stringBuffer, 0, stringBufferTop);
}
private void addToString(int c) {
int N = stringBufferTop;
if (N == stringBuffer.length) {
char[] tmp = new char[stringBuffer.length * 2];
System.arraycopy(stringBuffer
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>_THIS = DiagnosticType.warning(
"JSC_USED_GLOBAL_THIS",
"dangerous use of the global 'this' object");
private final AbstractCompiler compiler;
/**
* If {@code assignLhsChild != null}, then the node being traversed is
* a descendant of the first child of an ASSIGN node. assignLhsChild's
* parent is this ASSIGN node.
*/
private Node assignLhsChild = null;
CheckGlobalThis(AbstractCompiler compiler) {
this.compiler = compiler;
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
// Don't traverse functions that are constructors or have the @this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> }
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
/**
* Information about the context in which a Definition is used.
* Includes the referring node, and context in which the reference
* occurs - including the module in which the reference appears.
*
*/
class UseSite {
final Node node;
final Scope scope;
final JSModule module;
UseSite(Node node, Scope scope, JSModule module) {
this.node = node;
this.scope = scope;
this.module = module;
}
// Use the node as the identifying feature to make the UseSite recreatable.
@Override
public int hashCode() {
return this.node.hashCode();
}
@Override
public boolean equals(Object o) {
return (o instanceof UseSite && ((UseSite)(o)).node.equals(this.node));
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* NodeTraversal allows an iteration through the nodes in the parse tree,
* and facilitates the optimizations on the parse tree.
*
*/
public class NodeTraversal {
// Package protected for tests
private final AbstractCompiler compiler;
private final Callback callback;
/** Contains the current node*/
private Node curNode;
public static final DiagnosticType NODE_TRAVERSAL_ERROR =
DiagnosticType.error("JSC_NODE_TRAVERSAL_ERROR", "{0}");
/**
* Stack containing the Scopes that have been created. The Scope objects
* are lazily created; so the {@code scopeRoots} stack contains the
* Nodes for all Scopes that have not been created yet.
*/
private final Deque<Scope> scopes = new ArrayDeque<Scope>();
/**
* A stack of scope roots. All scopes that have not been created
* are represented in this Deque.
*/
private final Deque<Node> scopeRoots = new ArrayDeque<Node>();
/**
* Stack of control flow graphs (CFG). There is one CFG per scope. CFGs
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
inputId = NodeUtil.getInputId(scopeRoot);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
String sourceFileName = getBestSourceFileName(n);
if (sourceFileName == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceFileName, lineNumber);
if (src == null) {
src = MISSING_SOURCE;
}
return sourceFileName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
inputId = null;
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.isFunction()) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> }
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
*/
public CompilerInput getInput() {
return compiler.getInput(inputId);
}
/**
* Gets the current
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Expression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.isBlock(), body);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
/**
* Determines whether the traversal is currently in the global scope.
*/
boolean inGlobalScope() {
return getScopeDepth() <= 1;
}
int getScopeDepth() {
return scopes.size() + scopeRoots.size();
}
public boolean hasScope() {
return !(scopes.isEmpty() && scopeRoots.isEmpty());
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(getBestSourceFileName(n),
n, diagnosticType, arguments);
compiler.report(error);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return name == null ? "" : name;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make(getBestSourceFileName(n), n, level, type, arguments);
}
/**
* Creates a JSError during Node
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>));
block.addChildToBack(stmt);
}
return block;
}
public static Node script(List<Node> stmts) {
Node paramList = script();
for (Node stmt : stmts) {
Preconditions.checkState(mayBeStatementNoReturn(stmt));
paramList.addChildToBack(stmt);
}
return paramList;
}
public static Node var(Node name, Node value) {
Preconditions.checkState(name.isName() && !name.hasChildren());
Preconditions.checkState(mayBeExpression(value));
name.addChildToFront(value);
return var(name);
}
public static Node var(Node name) {
Preconditions.checkState(name.isName());
return new Node(Token.VAR, name);
}
public static Node returnNode() {
return new Node(Token.RETURN);
}
public static Node returnNode(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.RETURN, expr);
}
public static Node throwNode(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.THROW, expr);
}
public static Node exprResult(Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.EXPR_RESULT, expr);
}
public static Node ifNode(Node cond, Node then) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(then.isBlock());
return new Node(Token.IF, cond, then);
}
public static Node ifNode(Node cond, Node then, Node elseNode) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(then.isBlock());
Preconditions.checkState(elseNode.isBlock());
return new Node(Token.IF, cond, then, elseNode);
}
public static Node doNode(Node body, Node cond) {
Preconditions.checkState(body.isBlock());
Preconditions.checkState(mayBeExpression(cond));
return new Node(Token.DO, body, cond);
}
public static Node forIn(Node target, Node cond, Node body) {
Preconditions.checkState(target.isVar() ||
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
Node catchBody = block().copyInformationFrom(tryBody);
return new Node(Token.TRY, tryBody, catchBody, finallyBody);
}
public static Node tryCatch(Node tryBody, Node catchNode) {
Preconditions.checkState(tryBody.isBlock());
Preconditions.checkState(catchNode.isCatch());
Node catchBody = blockUnchecked(catchNode).copyInformationFrom(catchNode);
return new Node(Token.TRY, tryBody, catchBody);
}
public static Node tryCatchFinally(
Node tryBody, Node catchNode, Node finallyBody) {
Preconditions.checkState(finallyBody.isBlock());
Node tryNode = tryCatch(tryBody, catchNode);
tryNode.addChildToBack(finallyBody);
return tryNode;
}
public static Node catchNode(Node expr, Node body) {
Preconditions.checkState(expr.isName());
Preconditions.checkState(body.isBlock());
return new Node(Token.CATCH, expr, body);
}
public static Node breakNode() {
return new Node(Token.BREAK);
}
public static Node breakNode(Node name) {
// TODO(johnlenz): additional validation here.
Preconditions.checkState(name.isLabelName());
return new Node(Token.BREAK, name);
}
public static Node continueNode() {
return new Node(Token.CONTINUE);
}
public static Node continueNode(Node name) {
// TODO(johnlenz): additional validation here.
Preconditions.checkState(name.isLabelName());
return new Node(Token.CONTINUE, name);
}
//
public static Node call(Node target, Node ... args) {
Node call = new Node(Token.CALL, target);
for (Node arg : args) {
Preconditions.checkState(mayBeExpression(arg));
call.addChildToBack(arg);
}
return call;
}
public static Node newNode(Node target, Node ... args) {
Node newcall = new Node(Token.NEW, target);
for (Node arg : args) {
Preconditions.checkState(mayBeExpression(arg));
newcall.addChildToBack(arg);
}
return newcall;
}
public static Node name(String name) {
return Node.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>newString(Token.NAME, name);
}
public static Node getprop(Node target, Node prop) {
Preconditions.checkState(mayBeExpression(target));
Preconditions.checkState(prop.isString());
return new Node(Token.GETPROP, target, prop);
}
public static Node getelem(Node target, Node elem) {
Preconditions.checkState(mayBeExpression(target));
Preconditions.checkState(mayBeExpression(elem));
return new Node(Token.GETELEM, target, elem);
}
public static Node assign(Node target, Node expr) {
Preconditions.checkState(isAssignmentTarget(target));
Preconditions.checkState(mayBeExpression(expr));
return new Node(Token.ASSIGN, target, expr);
}
public static Node hook(Node cond, Node trueval, Node falseval) {
Preconditions.checkState(mayBeExpression(cond));
Preconditions.checkState(mayBeExpression(trueval));
Preconditions.checkState(mayBeExpression(falseval));
return new Node(Token.HOOK, cond, trueval, falseval);
}
public static Node comma(Node expr1, Node expr2) {
return binaryOp(Token.COMMA, expr1, expr2);
}
public static Node and(Node expr1, Node expr2) {
return binaryOp(Token.AND, expr1, expr2);
}
public static Node or(Node expr1, Node expr2) {
return binaryOp(Token.OR, expr1, expr2);
}
public static Node not(Node expr1) {
return unaryOp(Token.NOT, expr1);
}
/**
* "=="
*/
public static Node eq(Node expr1, Node expr2) {
return binaryOp(Token.EQ, expr1, expr2);
}
/**
* "==="
*/
public static Node sheq(Node expr1, Node expr2) {
return binaryOp(Token.SHEQ, expr1, expr2);
}
public static Node voidNode(Node expr1) {
return unaryOp(Token.VOID, expr1);
}
public static Node neg(Node expr1) {
return unaryOp(Token.NEG, expr1);
}
public static Node pos(Node expr1)
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>BeStatementNoReturn(n)) {
return n.isReturn();
}
return true;
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeExpression(Node n) {
switch (n.getType()) {
case Token.FUNCTION:
// FUNCTION is used both in expression and statement
// contexts.
return true;
case Token.ADD:
case Token.AND:
case Token.ARRAYLIT:
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.BITAND:
case Token.BITOR:
case Token.BITNOT:
case Token.BITXOR:
case Token.CALL:
case Token.COMMA:
case Token.DEC:
case Token.DELPROP:
case Token.DIV:
case Token.EQ:
case Token.FALSE:
case Token.GE:
case Token.GETPROP:
case Token.GETELEM:
case Token.GT:
case Token.HOOK:
case Token.IN:
case Token.INC:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NAME:
case Token.NE:
case Token.NEG:
case Token.NEW:
case Token.NOT:
case Token.NUMBER:
case Token.NULL:
case Token.OBJECTLIT:
case Token.OR:
case Token.POS:
case Token.REGEXP:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.STRING:
case Token.SUB:
case Token.THIS:
case Token.TYPEOF:
case Token.TRUE:
case Token.URSH:
case Token.VOID:
return true;
default:
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> private final Set<String> exposedProperties = Sets.newHashSet();
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isExprAssign(n)) {
Node assign = n.getFirstChild();
Node lhs = assign.getFirstChild();
if (lhs.isGetProp() && isMarkedExpose(assign)) {
exposedProperties.add(lhs.getLastChild().getString());
}
} else if (n.isStringKey() && isMarkedExpose(n)) {
exposedProperties.add(n.getString());
}
}
private boolean isMarkedExpose(Node n) {
JSDocInfo info = n.getJSDocInfo();
return info != null && info.isExpose();
}
}
/**
* Rewrite all exposed properties in [] form.
*/
private class RewriteExposedProperties
extends AbstractPostOrderCallback {
private final Set<String> exposedProperties;
RewriteExposedProperties(Set<String> exposedProperties) {
this.exposedProperties = exposedProperties;
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
String propName = n.getLastChild().getString();
if (exposedProperties.contains(propName)) {
Node obj = n.removeFirstChild();
Node prop = n.removeFirstChild();
n.getParent().replaceChild(n, IR.getelem(obj, prop));
compiler.reportCodeChange();
}
} else if (n.isStringKey()) {
String propName = n.getString();
if (exposedProperties.contains(propName)) {
n.setQuotedString();
compiler.reportCodeChange();
}
}
}
}
/**
* Propagate constant annotations over the Var graph.
*/
static class PropagateConstantAnnotationsOverVars
extends AbstractPostOrderCallback
implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
PropagateConstantAnnotationsOverVars(
AbstractCompiler compiler, boolean forbidChanges) {
this.compiler = compiler;
this.assertOnChange = forbidChanges;
}
@Override
public void process(Node externs, Node root) {
new NodeTraversal(compiler, this).traverseRoots(externs,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> root);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Note: Constant properties annotations are not propagated.
if (n.isName()) {
if (n.getString().isEmpty()) {
return;
}
JSDocInfo info = null;
// Find the JSDocInfo for a top-level variable.
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
boolean shouldBeConstant =
(info != null && info.isConstant()) ||
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent);
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (shouldBeConstant && !isMarkedConstant) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name + "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
}
/**
* Walk the AST tree and verify that constant names are used consistently.
*/
static class VerifyConstants extends AbstractPostOrderCallback
implements CompilerPass {
final private AbstractCompiler compiler;
final private boolean checkUserDeclarations;
VerifyConstants(AbstractCompiler compiler, boolean checkUserDeclarations) {
this.compiler = compiler;
this.checkUserDeclarations = checkUserDeclarations;
}
@Override
public void process(Node externs, Node root) {
Node externsAndJs = root.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(externsAndJs.hasChild(externs));
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
private Map<String, Boolean> constantMap = Maps.newHashMap();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
String name = n.getString();
if (n.getString().isEmpty()) {
return;
}
boolean isConst = n.getBooleanProp(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Node.IS_CONSTANT_NAME);
if (checkUserDeclarations) {
boolean expectedConst = false;
CodingConvention convention = compiler.getCodingConvention();
if (NodeUtil.isConstantName(n)
|| NodeUtil.isConstantByConvention(convention, n, parent)) {
expectedConst = true;
} else {
expectedConst = false;
JSDocInfo info = null;
Var var = t.getScope().getVar(n.getString());
if (var != null) {
info = var.getJSDocInfo();
}
if (info != null && info.isConstant()) {
expectedConst = true;
} else {
expectedConst = false;
}
}
if (expectedConst) {
Preconditions.checkState(expectedConst == isConst,
"The name %s is not annotated as constant.", name);
} else {
Preconditions.checkState(expectedConst == isConst,
"The name %s should not be annotated as constant.", name);
}
}
Boolean value = constantMap.get(name);
if (value == null) {
constantMap.put(name, isConst);
} else {
Preconditions.checkState(value.booleanValue() == isConst,
"The name %s is not consistently annotated as constant.", name);
}
}
}
}
/**
* Simplify the AST:
* - VAR declarations split, so they represent exactly one child
* declaration.
* - WHILEs are converted to FORs
* - FOR loop are initializers are moved out of the FOR structure
* - LABEL node of children other than LABEL, BLOCK, WHILE, FOR, or DO are
* moved into a block.
* - Add constant annotations based on coding convention.
*/
static class NormalizeStatements implements Callback {
private final AbstractCompiler compiler;
private final boolean assertOnChange;
NormalizeStatements(AbstractCompiler compiler, boolean assertOnChange) {
this.compiler = compiler;
this.assertOnChange = assertOnChange;
}
private void reportCodeChange(String changeDescription) {
if (assertOnChange) {
throw new IllegalStateException(
"Normalize constraints violated:\n" + changeDescription);
}
compiler.reportCodeChange();
}
@Override
public boolean shouldTraverse(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>NodeTraversal t, Node n, Node parent) {
doStatementNormalizations(t, n, parent);
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.WHILE:
if (CONVERT_WHILE_TO_FOR) {
Node expr = n.getFirstChild();
n.setType(Token.FOR);
Node empty = IR.empty();
empty.copyInformationFrom(n);
n.addChildBefore(empty, expr);
n.addChildAfter(empty.cloneNode(), expr);
reportCodeChange("WHILE node");
}
break;
case Token.FUNCTION:
normalizeFunctionDeclaration(n);
break;
case Token.NAME:
case Token.STRING:
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
if (!compiler.getLifeCycleStage().isNormalizedObfuscated()) {
annotateConstantsByConvention(n, parent);
}
break;
case Token.CAST:
parent.replaceChild(n, n.removeFirstChild());
break;
}
}
/**
* Mark names and properties that are constants by convention.
*/
private void annotateConstantsByConvention(Node n, Node parent) {
Preconditions.checkState(
n.isName()
|| n.isString()
|| n.isStringKey()
|| n.isGetterDef()
|| n.isSetterDef());
// There are only two cases where a string token
// may be a variable reference: The right side of a GETPROP
// or an OBJECTLIT key.
boolean isObjLitKey = NodeUtil.isObjectLitKey(n, parent);
boolean isProperty = isObjLitKey ||
(parent.isGetProp() &&
parent.getLastChild() == n);
if (n.isName() || isProperty) {
boolean isMarkedConstant = n.getBooleanProp(Node.IS_CONSTANT_NAME);
if (!isMarkedConstant &&
NodeUtil.isConstantByConvention(
compiler.getCodingConvention(), n, parent)) {
if (assertOnChange) {
String name = n.getString();
throw new IllegalStateException(
"Unexpected const change.\n" +
" name: "+ name +
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> "\n" +
" parent:" + n.getParent().toStringTree());
}
n.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
}
/**
* Rewrite named unhoisted functions declarations to a known
* consistent behavior so we don't to different logic paths for the same
* code. From:
* function f() {}
* to:
* var f = function () {};
*/
private void normalizeFunctionDeclaration(Node n) {
Preconditions.checkState(n.isFunction());
if (!NodeUtil.isFunctionExpression(n)
&& !NodeUtil.isHoistedFunctionDeclaration(n)) {
rewriteFunctionDeclaration(n);
}
}
/**
* Rewrite the function declaration from:
* function x() {}
* FUNCTION
* NAME
* LP
* BLOCK
* to:
* var x = function() {};
* VAR
* NAME
* FUNCTION
* NAME (w/ empty string)
* LP
* BLOCK
*/
private void rewriteFunctionDeclaration(Node n) {
// Prepare a spot for the function.
Node oldNameNode = n.getFirstChild();
Node fnNameNode = oldNameNode.cloneNode();
Node var = IR.var(fnNameNode).srcref(n);
// Prepare the function
oldNameNode.setString("");
// Move the function
Node parent = n.getParent();
parent.replaceChild(n, var);
fnNameNode.addChildToFront(n);
reportCodeChange("Function declaration");
}
/**
* Do normalizations that introduce new siblings or parents.
*/
private void doStatementNormalizations(
NodeTraversal t, Node n, Node parent) {
if (n.isLabel()) {
normalizeLabels(n);
}
// Only inspect the children of SCRIPTs, BLOCKs and LABELs, as all these
// are the only legal place for VARs and FOR statements.
if (NodeUtil.isStatementBlock(n) || n.isLabel()) {
extractForInitializer(n, null, null);
}
// Only inspect the children of SCRIPTs, BLOCKs, as all these
// are the only legal place for VARs.
if (NodeUtil.isStatementBlock
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {}
// to:
// var a = 1; for (a in b) {};
Node newStatement = first;
// Clone just the node, to remove any initialization.
Node name = newStatement.getFirstChild().cloneNode();
first.getParent().replaceChild(first, name);
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR-IN var declaration");
}
} else if (!c.getFirstChild().isEmpty()) {
Node init = c.getFirstChild();
Node empty = IR.empty();
empty.copyInformationFrom(c);
c.replaceChild(init, empty);
Node newStatement;
// Only VAR statements, and expressions are allowed,
// but are handled differently.
if (init.isVar()) {
newStatement = init;
} else {
newStatement = NodeUtil.newExpr(init);
}
insertBeforeParent.addChildBefore(newStatement, insertBefore);
reportCodeChange("FOR initializer");
}
break;
}
}
}
/**
* Split a var node such as:
* var a, b;
* into individual statements:
* var a;
* var b;
* @param n The whose children we should inspect.
*/
private void splitVarDeclarations(Node n) {
for (Node next, c = n.getFirstChild(); c != null; c = next) {
next = c.getNext();
if (c.isVar()) {
if (assertOnChange && !c.hasChildren()) {
throw new IllegalStateException("Empty VAR node.");
}
while (c.getFirstChild() != c.getLastChild()) {
Node name = c.getFirstChild();
c.removeChild(name);
Node newVar = IR.var(name).srcref(n);
n.addChildBefore(newVar, c);
reportCodeChange("VAR with multiple children");
}
}
}
}
/**
* Move all the functions that are valid at the execution of the first
* statement of the function to the beginning of the function definition.
*/
private void moveNamedFunctions(Node functionBody) {
Preconditions.checkState(
functionBody.getParent().isFunction());
Node previous = null;
Node current = functionBody.getFirstChild();
// Skip any
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> declarations at the beginning of the function body, they
// are already in the right place.
while (current != null && NodeUtil.isFunctionDeclaration(current)) {
previous = current;
current = current.getNext();
}
// Find any remaining declarations and move them.
Node insertAfter = previous;
while (current != null) {
// Save off the next node as the current node maybe removed.
Node next = current.getNext();
if (NodeUtil.isFunctionDeclaration(current)) {
// Remove the declaration from the body.
Preconditions.checkNotNull(previous);
functionBody.removeChildAfter(previous);
// Read the function at the top of the function body (after any
// previous declarations).
insertAfter = addToFront(functionBody, current, insertAfter);
reportCodeChange("Move function declaration not at top of function");
} else {
// Update the previous only if the current node hasn't been moved.
previous = current;
}
current = next;
}
}
/**
* @param after The child node to insert the newChild after, or null if
* newChild should be added to the front of parent's child list.
* @return The inserted child node.
*/
private Node addToFront(Node parent, Node newChild, Node after) {
if (after == null) {
parent.addChildToFront(newChild);
} else {
parent.addChildAfter(newChild, after);
}
return newChild;
}
}
/**
* Remove duplicate VAR declarations.
*/
private void removeDuplicateDeclarations(Node externs, Node root) {
Callback tickler = new ScopeTicklingCallback();
ScopeCreator scopeCreator = new SyntacticScopeCreator(
compiler, new DuplicateDeclarationHandler());
NodeTraversal t = new NodeTraversal(compiler, tickler, scopeCreator);
t.traverseRoots(externs, root);
}
/**
* ScopeCreator duplicate declaration handler.
*/
private final class DuplicateDeclarationHandler implements
SyntacticScopeCreator.RedeclarationHandler {
private Set<Var> hasOkDuplicateDeclaration = Sets.newHashSet();
/**
* Remove duplicate VAR declarations encountered discovered during
* scope creation.
*/
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Preconditions.checkState(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>n.isName());
Node parent = n.getParent();
Var v = s.getVar(name);
if (v != null && s.isGlobal()) {
// We allow variables to be duplicate declared if one
// declaration appears in source and the other in externs.
// This deals with issues where a browser built-in is declared
// in one browser but not in another.
if (v.isExtern() && !input.isExtern()) {
if (hasOkDuplicateDeclaration.add(v)) {
return;
}
}
}
// If name is "arguments", Var maybe null.
if (v != null && v.getParentNode().isCatch()) {
// Redeclaration of a catch expression variable is hard to model
// without support for "with" expressions.
// The ECMAScript spec (section 12.14), declares that a catch
// "catch (e) {}" is handled like "with ({'e': e}) {}" so that
// "var e" would refer to the scope variable, but any following
// reference would still refer to "e" of the catch expression.
// Until we have support for this disallow it.
// Currently the Scope object adds the catch expression to the
// function scope, which is technically not true but a good
// approximation for most uses.
// TODO(johnlenz): Consider improving how scope handles catch
// expression.
// Use the name of the var before it was made unique.
name = MakeDeclaredNamesUnique.ContextualRenameInverter.getOrginalName(
name);
compiler.report(
JSError.make(
input.getName(), n,
CATCH_BLOCK_VAR_ERROR, name));
} else if (v != null && parent.isFunction()) {
if (v.getParentNode().isVar()) {
s.undeclare(v);
s.declare(name, n, n.getJSType(), v.input);
replaceVarWithAssignment(v.getNameNode(), v.getParentNode(),
v.getParentNode().getParent());
}
} else if (parent.isVar()) {
Preconditions.checkState(parent.hasOneChild());
replaceVarWithAssignment(n, parent, parent.getParent());
}
}
/**
* Remove the parent VAR. There
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> */
public interface StaticScope<T> {
/**
* Returns the root node associated with this scope. May be null.
*/
Node getRootNode();
/** Returns the scope enclosing this one or null if none. */
StaticScope<T> getParentScope();
/**
* Returns any defined slot within this scope for this name. This call
* continues searching through parent scopes if a slot with this name is not
* found in the current scope.
* @param name The name of the variable slot to look up.
* @return The defined slot for the variable, or {@code null} if no
* definition exists.
*/
StaticSlot<T> getSlot(String name);
/** Like {@code getSlot} but does not recurse into parent scopes. */
StaticSlot<T> getOwnSlot(String name);
/** Returns the expected type of {@code this} in the current scope. */
T getTypeOfThis();
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> an externs input) by input id.
* May return null.
*/
public abstract CompilerInput getInput(InputId inputId);
/**
* Looks up a source file by name. May return null.
*/
abstract SourceFile getSourceFileByName(String sourceName);
/**
* Creates a new externs file.
* @param name A name for the new externs file.
* @throws IllegalArgumentException If the name of the externs file conflicts
* with a pre-existing externs file.
*/
abstract CompilerInput newExternInput(String name);
/**
* Gets the module graph. May return null if there aren't at least two
* modules.
*/
abstract JSModuleGraph getModuleGraph();
/**
* Gets the inputs in the order in which they are being processed.
* Only for use by {@code AbstractCompilerRunner}.
*/
abstract List<CompilerInput> getInputsInOrder();
/**
* Gets a central registry of type information from the compiled JS.
*/
public abstract JSTypeRegistry getTypeRegistry();
/**
* Gets a memoized scope creator with type information.
*/
abstract ScopeCreator getTypedScopeCreator();
/**
* Gets the top scope.
*/
public abstract Scope getTopScope();
/**
* Report an error or warning.
*/
public abstract void report(JSError error);
/**
* Report an internal error.
*/
abstract void throwInternalError(String msg, Exception cause);
/**
* Gets the current coding convention.
*/
public abstract CodingConvention getCodingConvention();
/**
* Report code changes.
*/
public abstract void reportCodeChange();
/**
* Logs a message under a central logger.
*/
abstract void addToDebugLog(String message);
/**
* Sets the CssRenamingMap.
*/
abstract void setCssRenamingMap(CssRenamingMap map);
/**
* Gets the CssRenamingMap.
*/
abstract CssRenamingMap getCssRenamingMap();
/**
* Gets a suitable SCRIPT node to serve as a parent for code insertion. If
* {@code module} contains any inputs, the returned node will be the SCRIPT
* node corresponding to its first input. If {@code module} is empty, on the
* other hand, then the returned node will be the first SCRIPT node in a
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.graph.DiGraph.DiGraphEdge;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.LatticeElement;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Computes "may be" reaching use for all definitions of each variables.
*
* A use of {@code A} in {@code alert(A)} is a "may be" reaching use of
* the definition of {@code A} at {@code A = foo()} if at least one path from
* the use node reaches that definition and it is the last definition before
* the use on that path.
*
*/
class MaybeReachingVariableUse extends
DataFlowAnalysis<Node, MaybeReachingVariableUse.ReachingUses> {
// The scope of the function that we are analyzing.
private final Scope jsScope;
private final Set<Var> escaped;
MaybeReachingVariableUse(
ControlFlowGraph<Node> cfg
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>, Scope jsScope, AbstractCompiler compiler) {
super(cfg, new ReachingUsesJoinOp());
this.jsScope = jsScope;
this.escaped = Sets.newHashSet();
// TODO(user): Maybe compute it somewhere else and re-use the escape
// local set here.
computeEscaped(jsScope, escaped, compiler);
}
/**
* May use definition lattice representation. It captures a product
* lattice for each local (non-escaped) variable. The sub-lattice is
* a n + 2 power set element lattice with all the Nodes in the program,
* TOP and BOTTOM. This is better explained with an example:
*
* Consider: A sub-lattice element representing the variable A represented
* by { N_4, N_5} where N_x is a Node in the program. This implies at
* that particular point in the program the content of A is "upward exposed"
* at point N_4 and N_5.
*
* Example:
*
* A = 1;
* ...
* N_3:
* N_4: print(A);
* N_5: y = A;
* N_6: A = 1;
* N_7: print(A);
*
* At N_3, reads of A in {N_4, N_5} are said to be upward exposed.
*/
static final class ReachingUses implements LatticeElement {
final Multimap<Var, Node> mayUseMap;
public ReachingUses() {
mayUseMap = HashMultimap.create();
}
/**
* Copy constructor.
*
* @param other The constructed object is a replicated copy of this element.
*/
public ReachingUses(ReachingUses other) {
mayUseMap = HashMultimap.create(other.mayUseMap);
}
@Override
public boolean equals(Object other) {
return (other instanceof ReachingUses) &&
((ReachingUses) other).mayUseMap.equals(this.mayUseMap);
}
@Override
public int hashCode() {
return mayUseMap.hashCode();
}
}
/**
* The join is a simple union because of the "may be" nature of the analysis.
*
* Consider:
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> A = 1; if (x) { A = 2 }; alert(A);
*
* The read of A "may be" exposed to A = 1 in the beginning.
*/
private static class ReachingUsesJoinOp implements JoinOp<ReachingUses> {
@Override
public ReachingUses apply(List<ReachingUses> from) {
ReachingUses result = new ReachingUses();
for (ReachingUses uses : from) {
result.mayUseMap.putAll(uses.mayUseMap);
}
return result;
}
}
@Override
boolean isForward() {
return false;
}
@Override
ReachingUses createEntryLattice() {
return new ReachingUses();
}
@Override
ReachingUses createInitialEstimateLattice() {
return new ReachingUses();
}
@Override
ReachingUses flowThrough(Node n, ReachingUses input) {
ReachingUses output = new ReachingUses(input);
// If there's an ON_EX edge, this cfgNode may or may not get executed.
// We can express this concisely by just pretending this happens in
// a conditional.
boolean conditional = hasExceptionHandler(n);
computeMayUse(n, n, output, conditional);
return output;
}
private boolean hasExceptionHandler(Node cfgNode) {
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(cfgNode);
for (DiGraphEdge<Node, Branch> edge : branchEdges) {
if (edge.getValue() == Branch.ON_EX) {
return true;
}
}
return false;
}
private void computeMayUse(
Node n, Node cfgNode, ReachingUses output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.NAME:
addToUseIfLocal(n.getString(), cfgNode, output);
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeMayUse(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMayUse(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (lhs.isVar()) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
if (lhs.isName() && !conditional) {
removeFromUseIfLocal(lhs.getString(), output);
}
computeMayUse(rhs, cfgNode, output, conditional);
}
return;
case Token.AND:
case Token.OR:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.HOOK:
computeMayUse(n.getLastChild(), cfgNode, output, true);
computeMayUse(n.getFirstChild().getNext(), cfgNode, output, true);
computeMayUse(n.getFirstChild(), cfgNode, output, conditional);
return;
case Token.VAR:
Node varName = n.getFirstChild();
Preconditions.checkState(n.hasChildren(), "AST should be normalized");
if (varName.hasChildren()) {
computeMayUse(varName.getFirstChild(), cfgNode, output, conditional);
if (!conditional) {
removeFromUseIfLocal(varName.getString(), output);
}
}
return;
default:
if (NodeUtil.isAssignmentOp(n) && n.getFirstChild().isName()) {
Node name = n.getFirstChild();
if (!conditional) {
removeFromUseIfLocal(name.getString(), output);
}
// In case of a += "Hello". There is a read of a.
if (!n.isAssign()) {
addToUseIfLocal(name.getString(), cfgNode, output);
}
computeMayUse(name.getNext(), cfgNode, output, conditional);
} else {
/*
* We want to traverse in reverse order because we want the LAST
* definition in the sub-tree....
* But we have no better way to traverse in reverse other :'(
*/
for (Node c = n.getLastChild(); c != null; c = n.getChildBefore(c)) {
computeMayUse
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(c, cfgNode, output, conditional);
}
}
}
}
/**
* Sets the variable for the given name to the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void addToUseIfLocal(String name, Node node, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUseMap.put(var, node);
}
}
/**
* Removes the variable for the given name from the node value in the upward
* exposed lattice. Do nothing if the variable name is one of the escaped
* variable.
*/
private void removeFromUseIfLocal(String name, ReachingUses use) {
Var var = jsScope.getVar(name);
if (var == null || var.scope != jsScope) {
return;
}
if (!escaped.contains(var)) {
use.mayUseMap.removeAll(var);
}
}
/**
* Gets a list of nodes that may be using the value assigned to {@code name}
* in {@code defNode}. {@code defNode} must be one of the control flow graph
* nodes.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param defNode The list of upward exposed use for the variable.
*/
Collection<Node> getUses(String name, Node defNode) {
GraphNode<Node, Branch> n = getCfg().getNode(defNode);
Preconditions.checkNotNull(n);
FlowState<ReachingUses> state = n.getAnnotation();
return state.getOut().mayUseMap.get(jsScope.getVar(name));
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>TypeNative.
private final boolean isChecked;
UnknownType(JSTypeRegistry registry, boolean isChecked) {
super(registry);
this.isChecked = isChecked;
}
@Override
public boolean isUnknownType() {
return true;
}
@Override
public boolean isCheckedUnknownType() {
return isChecked;
}
@Override
public boolean canBeCalled() {
return true;
}
@Override
public boolean matchesNumberContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public TernaryValue testForEquality(JSType that) {
return UNKNOWN;
}
@Override
public boolean isNullable() {
return true;
}
@Override
public boolean isSubtype(JSType that) {
return true;
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnknownType();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseUnknownType(this, that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return getReferenceName();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
// nothing to define
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public String getReferenceName() {
return isChecked ? "??" : "?";
}
@Override
public String getDisplayName() {
return "Unknown";
}
@Override
public boolean hasDisplayName() {
return true;
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other JS files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isName()) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
Preconditions.checkState(NodeUtil.isFunctionExpression(parent));
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (!sanityCheck &&
varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = IR.name(varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, I'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
IR.var(nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.PARAM_LIST:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
return compiler.getSynthesizedExternsInput();
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.keySet();
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
/**
* Gets the reference collection for the given variable.
*/
@Override
public ReferenceCollection getReferences(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(t, v, new Reference(n, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explicit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection implements Iterable<Reference> {
List<Reference> references = Lists.newArrayList();
@Override
public Iterator<Reference> iterator() {
return references.iterator();
}
void add(Reference reference, NodeTraversal t, Var v) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgment.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get(0).isDeclaration());
BasicBlock initBlock = init.getBasicBlock();
for (int i = 1; i < size; i++) {
if (!initBlock.provablyExecutesBefore(
references.get(i).getBasicBlock())) {
return false;
}
}
return true;
}
/**
* Whether the variable is escaped into an inner scope.
*/
boolean isEscaped() {
Scope scope = null;
for (Reference ref : references) {
if (scope == null) {
scope = ref.scope;
} else if (scope != ref.scope) {
return true;
}
}
return false;
}
/**
* @param index The index into the references array to look for an
* assigning declaration.
*
* This is either the declaration if a value is assigned (such as
* "var a = 2", "function a()...", "... catch (a)...").
*/
private boolean isInitializingDeclarationAt(int index) {
Reference maybeInit = references.get(index);
if (maybeInit.is
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> } else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference implements StaticReference<JSType> {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final BasicBlock basicBlock;
private final Scope scope;
private final InputId inputId;
private final StaticSourceFile sourceFile;
Reference(Node nameNode, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(),
basicBlock, t.getScope(), t.getInput().
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>getInputId());
}
/**
* Creates a variable reference in a given script file name, used in tests.
*
* @return The created reference.
*/
@VisibleForTesting
static Reference createRefForTest(CompilerInput input) {
return new Reference(new Node(Token.NAME), null, null,
input.getInputId());
}
private Reference(Node nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
Basic
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Block getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = p
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
UNKNOWN_OVERRIDE,
INTERFACE_METHOD_OVERRIDE,
UNKNOWN_EXPR_TYPE,
UNRESOLVED_TYPE,
WRONG_ARGUMENT_COUNT,
ILLEGAL_IMPLICIT_CAST,
INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
EXPECTED_THIS_TYPE,
IN_USED_WITH_STRUCT,
ILLEGAL_PROPERTY_CREATION,
ILLEGAL_OBJLIT_KEY,
RhinoErrorReporter.TYPE_PARSE_ERROR,
TypedScopeCreator.UNKNOWN_LENDS,
TypedScopeCreator.LENDS_ON_NON_OBJECT,
TypedScopeCreator.CTOR_INITIALIZER,
TypedScopeCreator.IFACE_INITIALIZER,
FunctionTypeBuilder.THIS_TYPE_NON_OBJECT);
private final AbstractCompiler compiler;
private final TypeValidator validator;
private final ReverseAbstractInterpreter reverseInterpreter;
private final JSTypeRegistry typeRegistry;
private Scope topScope;
private MemoizedScopeCreator scopeCreator;
private final CheckLevel reportMissingOverride;
private final CheckLevel reportUnknownTypes;
// This may be expensive, so don't emit these warnings if they're
// explicitly turned off.
private boolean reportMissingProperties = true;
private InferJSDocInfo inferJSDocInfo = null;
// These fields are used to calculate the percentage of expressions typed.
private int typedCount = 0;
private int nullCount = 0;
private int unknownCount = 0;
private boolean inExterns;
// A state boolean to see we are currently in @notypecheck section of the
// code.
private int noTypeCheckSection = 0;
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
Scope topScope,
MemoizedScopeCreator scopeCreator,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = reportUnknownTypes;
this.inferJSDocInfo
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = new InferJSDocInfo(compiler);
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride, reportUnknownTypes);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING, CheckLevel.OFF);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
@Override
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> }
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.CAST:
Node expr = n.getFirstChild();
ensureTyped(t, n, getJSType(expr));
// If the cast, tightens the type apply it, so it is available post
// normalization.
JSType castType = getJSType(n);
JSType exprType = getJSType(expr);
if (castType.isSubtype(exprType)) {
expr.setJSType(castType);
}
break;
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.PARAM_LIST:
typeable = false;
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.STRING_KEY:
typeable = false;
break;
case Token.GETTER_DEF:
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: {
left = n.getFirstChild();
right = n.getLastChild();
if (left.isTypeOf()) {
if (right.isString()) {
checkTypeofString(t, right, right.getString());
}
} else if (right.isTypeOf() && left.isString()) {
checkTypeofString(t, left, left.getString());
}
leftType = getJSType(left);
rightType = getJSType(right);
// We do not want to warn about explicit comparisons to VOID. People
// often do this if they think their type annotations screwed up.
//
// We do want to warn about cases where people compare things like
// (Array|null) == (Function|null)
// because it probably means they screwed up.
//
// This heuristic here is not perfect, but should catch cases we
// care about without too many false negatives.
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result = TernaryValue.UNKNOWN;
if (n.getType() == Token.EQ || n.getType() == Token.NE) {
result = leftTypeRestricted.testForEquality(rightTypeRestricted);
if (n.isNE()) {
result = result.not();
}
} else {
// SHEQ or SHNE
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
result = n.getType() == Token.SHEQ ?
TernaryValue.FALSE : TernaryValue.TRUE;
}
}
if (result != TernaryValue.UNKNOWN) {
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = get
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>UnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.isAssign()</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
// Check property sets to 'object.property' when 'object' is known.
if (lvalue.isGetProp()) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
Node property = lvalue.getLastChild();
String pname = property.getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.isGetProp()) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, pname, lvalue, rvalue);
}
}
checkEnumAlias(t, info, rvalue);
checkPropCreation(t, lvalue);
// Prototype assignments are special, because they actually affect
// the definition of a class. These are mostly validated
// during TypedScopeCreator, and we only look for the "dumb" cases here.
// object.prototype = ...;
if (pname.equals("prototype")) {
if (objectJsType != null && objectJsType.isFunctionType()) {
FunctionType functionType = objectJsType.toMaybeFunctionType();
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
// Only assign structs to the prototype of a @struct constructor
if (functionType.makesStructs() && !rvalueType.isStruct()) {
String funName = functionType.getTypeOfThis().toString();
compiler.report(t.makeError(assign, CONFLICTING_EXTENDED_TYPE,
"struct", funName));
}
return;
}
}
}
// The generic checks for 'object.property' when 'object' is known,
// and 'property' is declared on it.
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(pname) &&
!type.isPropertyTypeInferred(pname) &&
!propertyIsImplicitCast(type, pname)) {
JSType expectedType = type.getPropertyType(pname);
if (!expectedType.isUnknownType()) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
expectedType, object, pname);
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, pname, info, expectedType);
return;
}
}
}
// If we couldn't get the property type with normal object property
// lookups, then check inheritance anyway with the unknown type.
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, pname, info, getNativeType(UNKNOWN_TYPE));
}
// Check qualified name sets to 'object' and 'object.property'.
// This can sometimes handle cases when the type of 'object' is not known.
// e.g.,
// var obj = createUnknownType();
// /** @type {number} */ obj.foo = true;
JSType leftType = getJSType(lvalue);
if (lvalue.isQualifiedName()) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getQualifiedName());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
if (NodeUtil.getRootOfQualifiedName(lvalue).isThis
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>() &&
t.getScope() != var.getScope()) {
// Don't look at "this.foo" variables from other scopes.
return;
}
if (var.getType() != null) {
leftType = var.getType();
}
}
}
// Fall through case for arbitrary LHS and arbitrary RHS.
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/** Check that we don't create new properties on structs. */
private void checkPropCreation(NodeTraversal t, Node lvalue) {
if (lvalue.isGetProp()) {
Node obj = lvalue.getFirstChild();
Node prop = lvalue.getLastChild();
JSType objType = getJSType(obj);
String pname = prop.getString();
if (objType.isStruct() && !objType.hasProperty(pname)) {
if (!(obj.isThis() &&
getJSType(t.getScope().getRootNode()).isConstructor())) {
report(t, prop, ILLEGAL_PROPERTY_CREATION);
}
}
}
}
private void checkPropertyInheritanceOnGetpropAssign(
NodeTraversal t, Node assign, Node object, String property,
JSDocInfo info, JSType propertyType) {
// Inheritance checks for prototype properties.
//
// TODO(nicksantos): This isn't the right place to do this check. We
// really want to do this when we're looking at the constructor.
// We'd find all its properties and make sure they followed inheritance
// rules, like we currently do for @implements to make sure
// all the methods are implemented.
//
// As-is, this misses many other ways to override a property.
//
// object.prototype.property = ...;
if (object.isGetProp()) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = getJSType(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.PARAM_LIST ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
if (childType.isDict()) {
report(t, property, TypeValidator.ILLEGAL_PROPERTY_ACCESS, "'.'", "dict");
} else if (validator.expectNotNullOrUndefined(t, n, childType,
"No properties on this expression", getNativeType(OBJECT_TYPE))) {
checkPropertyAccess(childType, property.getString(), t, n);
}
ensureTyped(t, n);
}
/**
* Emit a warning if we can prove that a property cannot possibly be
* defined on an object. Note the difference between JS and a strictly
* statically typed language: we're checking if the property
* *cannot be defined*, whereas a java compiler would check if the
* property *can be undefined*.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>TestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
validator.expectIndexMatch(
t, n, getJSType(n.getFirstChild()), getJSType(n.getLastChild()));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
checkEnumAlias
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(t, info, value);
if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
JSType type = getJSType(constructor).restrictByNotNullOrUndefined();
if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
visitParameterList(t, n, fnType);
ensureTyped(t, n, fnType.getInstanceType());
} else {
ensureTyped(t, n);
}
} else {
report(t, n, NOT_A_CONSTRUCTOR);
ensureTyped(t, n);
}
}
/**
* Check whether there's any property conflict for for a particular super
* interface
* @param t The node traversal object that supplies context
* @param n The node being visited
* @param functionName The function name being checked
* @param properties The property names in the super interfaces that have
* been visited
* @param currentProperties The property names in the super interface
* that have been visited
* @param interfaceType The super interface that is being visited
*/
private void checkInterfaceConflictProperties(NodeTraversal t, Node n,
String functionName, HashMap<String, ObjectType> properties,
HashMap<String, ObjectType> currentProperties,
ObjectType interfaceType) {
Set<String> currentPropertyNames =
interfaceType.getImplicitPrototype().getOwnPropertyNames();
for (String name : currentPropertyNames) {
ObjectType oType = properties.get(name);
if (oType != null) {
if (!interfaceType.getPropertyType(name).isEquivalentTo(
oType.getPropertyType(name))) {
compiler.report(
t.makeError(n, INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
functionName, name, oType.toString(),
interfaceType.toString()));
}
}
currentProperties.put(name, interfaceType
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>);
}
for (ObjectType iType : interfaceType.getCtorExtendedInterfaces()) {
checkInterfaceConflictProperties(t, n, functionName, properties,
currentProperties, iType);
}
}
/**
* Visits a {@link Token#FUNCTION} node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitFunction(NodeTraversal t, Node n) {
FunctionType functionType = JSType.toMaybeFunctionType(n.getJSType());
String functionPrivateName = n.getFirstChild().getString();
if (functionType.isConstructor()) {
FunctionType baseConstructor = functionType.getSuperClassConstructor();
if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE) &&
baseConstructor != null &&
baseConstructor.isInterface()) {
compiler.report(
t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"constructor", functionPrivateName));
} else {
if (baseConstructor != getNativeType(OBJECT_FUNCTION_TYPE)) {
ObjectType proto = functionType.getPrototype();
if (functionType.makesStructs() && !proto.isStruct()) {
compiler.report(t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"struct", functionPrivateName));
} else if (functionType.makesDicts() && !proto.isDict()) {
compiler.report(t.makeError(n, CONFLICTING_EXTENDED_TYPE,
"dict", functionPrivateName));
}
}
// All interfaces are properly implemented by a class
for (JSType baseInterface : functionType.getImplementedInterfaces()) {
boolean badImplementedType = false;
ObjectType baseInterfaceObj = ObjectType.cast(baseInterface);
if (baseInterfaceObj != null) {
FunctionType interfaceConstructor =
baseInterfaceObj.getConstructor();
if (interfaceConstructor != null &&
!interfaceConstructor.isInterface()) {
badImplementedType = true;
}
} else {
badImplementedType = true;
}
if (badImplementedType) {
report(t, n, BAD_IMPLEMENTED_TYPE, functionPrivateName);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(!n.isFunction() ||
type.isFunctionType() ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
// TODO(johnlenz): Change this so that we only look for casts on CAST
// nodes one the misplaced type annotation warning is on by default and
// people have been given a chance to fix them. As is, this is here
// simply for legacy casts.
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.isGetProp() ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns the percentage of nodes typed by the type checker.
* @return a number
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.javascript.rhino.IR;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Set;
/**
* Models an assignment that defines a variable and the removal of it.
*
*/
class DefinitionsRemover {
/**
* @return an {@link Definition} object if the node contains a definition or
* {@code null} otherwise.
*/
static Definition getDefinition(Node n, boolean isExtern) {
// TODO(user): Since we have parent pointers handy. A lot of constructors
// can be simplified.
// This logic must match #isDefinitionNode
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) {
return new VarDefinition(n, isExtern);
} else if (parent.isFunction() && parent.getFirstChild() == n) {
if (!NodeUtil.isFunctionExpression(parent)) {
return new NamedFunctionDefinition(parent, isExtern);
} else if (!n.getString().equals("")) {
return new FunctionExpressionDefinition(parent, isExtern);
}
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return new AssignmentDefinition(parent, isExtern);
} else if (NodeUtil.isObjectLitKey(n, parent)) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
return new ObjectLiteralPropertyDefinition(parent, n, n.getFirstChild(),
isExtern);
} else if (parent.isParamList()) {
Node function = parent.getParent();
return new FunctionArgumentDefinition(function, n, isExtern);
}
return null;
}
/**
* @return Whether a definition object can be created.
*/
static boolean isDefinitionNode(Node n) {
// This logic must match #getDefinition
Node parent = n.getParent();
if (parent == null) {
return false;
}
if (NodeUtil.isVarDeclaration(n) && n.hasChildren()) {
return true;
} else if (parent.isFunction() && parent.getFirstChild() == n) {
if (!NodeUtil.isFunctionExpression(parent)) {
return true;
} else if (!n.getString().equals("")) {
return true;
}
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return true;
} else if (NodeUtil.isObjectLitKey(n, parent)) {
return true;
} else if (parent.isParamList()) {
return true;
}
return false;
}
static abstract class Definition {
private final boolean isExtern;
Definition(boolean isExtern) {
this.isExtern = isExtern;
}
/**
* Removes this definition from the AST if it is not an extern.
*
* This method should not be called on a definition for which isExtern()
* is true.
*/
public void remove() {
if (!isExtern) {
performRemove();
} else {
throw new IllegalStateException("Attempt to remove() an extern" +
" definition.");
}
}
/**
* Subclasses should override to remove the definition from the AST.
*/
protected abstract void performRemove();
/**
* Variable or property name represented by this definition.
* For example, in the case of assignments this method would
* return the NAME, GETPROP or GETELEM expression that acts as the
* assignment left hand side.
*
* @return the L-Value associated with this definition.
* The node's type is always NAME, GETPROP or GETELEM.
*/
public abstract Node getLValue();
/**
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> * Value expression that acts as the right hand side of the
* definition statement.
*/
public abstract Node getRValue();
/**
* Returns true if the definition is an extern.
*/
public boolean isExtern() {
return isExtern;
}
}
/**
* Represents an name-only external definition. The definition's
* RHS is missing.
*/
abstract static class IncompleteDefinition extends Definition {
private static final Set<Integer> ALLOWED_TYPES =
ImmutableSet.of(Token.NAME, Token.GETPROP, Token.GETELEM);
private final Node lValue;
IncompleteDefinition(Node lValue, boolean inExterns) {
super(inExterns);
Preconditions.checkNotNull(lValue);
Preconditions.checkArgument(ALLOWED_TYPES.contains(lValue.getType()),
"Unexpected lValue type %s", Token.name(lValue.getType()));
this.lValue = lValue;
}
@Override
public Node getLValue() {
return lValue;
}
@Override
public Node getRValue() {
return null;
}
}
/**
* Represents an unknown definition.
*/
static final class UnknownDefinition extends IncompleteDefinition {
UnknownDefinition(Node lValue, boolean inExterns) {
super(lValue, inExterns);
}
@Override
public void performRemove() {
throw new IllegalArgumentException("Can't remove an UnknownDefinition");
}
}
/**
* Represents an name-only external definition. The definition's
* RHS is missing.
*/
static final class ExternalNameOnlyDefinition extends IncompleteDefinition {
ExternalNameOnlyDefinition(Node lValue) {
super(lValue, true);
}
@Override
public void performRemove() {
throw new IllegalArgumentException(
"Can't remove external name-only definition");
}
}
/**
* Represents a function formal parameter. The definition's RHS is missing.
*/
static final class FunctionArgumentDefinition extends IncompleteDefinition {
FunctionArgumentDefinition(Node function,
Node argumentName,
boolean inExterns) {
super(argumentName, inExterns);
Preconditions.checkArgument(function.isFunction());
Preconditions.checkArgument(argumentName.isName());
}
@Override
public void performRemove()
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
* Example: var x = { e : function() { } };
*/
static final class ObjectLiteralPropertyDefinition extends Definition {
private final Node literal;
private final Node name;
private final Node value;
ObjectLiteralPropertyDefinition(Node lit, Node name, Node value,
boolean isExtern) {
super(isExtern);
this.literal = lit;
this.name = name;
this.value = value;
}
@Override
public void performRemove() {
literal.removeChild(name);
}
@Override
public Node getLValue() {
// TODO(user) revisit: object literal definitions are an example
// of definitions whose LHS doesn't correspond to a node that
// exists in the AST. We will have to change the return type of
// getLValue sooner or later in order to provide this added
// flexibility.
switch (name.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// TODO(johnlenz): return a GETELEM for quoted strings.
return IR.getprop(
IR.objectlit(),
IR.string(name.getString()));
default:
throw new IllegalStateException("unexpected");
}
}
@Override
public Node getRValue() {
return value;
}
}
/**
* Represents a VAR declaration with an assignment.
*/
static final class VarDefinition extends Definition {
private final Node name;
VarDefinition(Node node, boolean inExterns) {
super(inExterns);
Preconditions.checkArgument(NodeUtil.isVarDeclaration(node));
Preconditions.checkArgument(node.hasChildren(),
"VAR Declaration of %sshould be assigned a value.", node.getString());
name = node;
}
@Override
public void performRemove() {
Node var = name.getParent();
Preconditions.checkState(var.getFirstChild() == var.getLastChild(),
"AST should be normalized first");
Node parent = var.getParent();
Node rValue = name.removeFirstChild();
Preconditions.checkState(!parent.isFor());
parent.replaceChild(var, NodeUtil.newExpr(rValue));
}
@Override
public Node getLValue() {
return name;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.isRegExp()) {
String pattern = n.getFirstChild().getString();
String flags = n.getChildCount() == 2
? n.getLastChild().getString() : "";
try {
RegExpTree.parseRegExp(pattern, flags);
} catch (IllegalArgumentException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> are valid
case com.google.javascript.rhino.head.Token.FUNCTION:
FunctionNode fnNode = (FunctionNode)node;
valid = fnNode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT;
break;
// Object literal properties and catch declarations are valid.
case com.google.javascript.rhino.head.Token.NAME:
valid = node.getParent() instanceof ObjectProperty
|| node.getParent() instanceof CatchClause
|| node.getParent() instanceof FunctionNode;
break;
// Object literal properties are valid
case com.google.javascript.rhino.head.Token.GET:
case com.google.javascript.rhino.head.Token.SET:
case com.google.javascript.rhino.head.Token.NUMBER:
case com.google.javascript.rhino.head.Token.STRING:
valid = node.getParent() instanceof ObjectProperty;
break;
// Property assignments are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.ASSIGN:
if (node instanceof Assignment) {
valid = isExprStmt(node.getParent())
&& isPropAccess(((Assignment)node).getLeft());
}
break;
// Property definitions are valid, if at the root of an expression.
case com.google.javascript.rhino.head.Token.GETPROP:
case com.google.javascript.rhino.head.Token.GETELEM:
valid = isExprStmt(node.getParent());
break;
}
if (!valid) {
errorReporter.warning(MISPLACED_TYPE_ANNOTATION,
sourceName,
node.getLineno(), "", 0);
}
}
}
private boolean isPropAccess(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.GETPROP
|| node.getType() == com.google.javascript.rhino.head.Token.GETELEM;
}
private boolean isExprStmt(AstNode node) {
return node.getType() == com.google.javascript.rhino.head.Token.EXPR_RESULT
|| node.getType() == com.google.javascript.rhino.head.Token.EXPR_VOID;
}
private Node transform(AstNode node
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.rhino.head.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* ECMA-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return nType == Token.EXPR_RESULT &&
n.getFirstChild().isString() &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
nameNode.getLineno(), "", 0);
}
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
}
/**
* @return Whether the
*/
private boolean isReservedKeyword(String identifier) {
return reservedKeywords != null && reservedKeywords.contains(identifier);
}
@Override
Node processNewExpression(NewExpression exprNode) {
Node node = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getTarget()));
for (AstNode child : exprNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(exprNode.getLineno());
node.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(node, exprNode);
return node;
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
key.setType(Token.STRING_KEY);
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GETTER_DEF);
Preconditions.checkState(value.isFunction());
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SETTER_DEF);
Preconditions.checkState(value.isFunction());
if (!getFnParamNode(value).hasOneChild()) {
reportSetterParam(el.getLeft());
}
}
key.addChildToFront(value);
node.addChildToBack(key);
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> return node;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
Node getFnParamNode(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
Node leftChild = transform(getNode.getTarget());
Node newNode = newNode(
Token.GETPROP, leftChild, transformAsString(getNode.getProperty()));
newNode.setLineno(leftChild.getLineno());
newNode.setCharno(leftChild.getCharno());
maybeSetLengthFrom(newNode, getNode);
return newNode;
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(literalStringNode, literalNode);
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
maybeSetLengthFrom(flagsNode, literalNode);
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> operand.getLineno(), "", 0);
} else if (type == Token.INC || type == Token.DEC) {
if (!validAssignmentTarget(operand)) {
String msg = (type == Token.INC)
? "invalid increment target"
: "invalid decrement target";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
}
}
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
private boolean validAssignmentTarget(Node target) {
switch (target.getType()) {
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
return true;
}
return false;
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
if (!config.acceptConstKeyword && declarationNode.getType() ==
com.google.javascript.rhino.head.Token.CONST) {
processIllegalToken(declarationNode);
}
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
Node initalizer = transform(initializerNode.getInitializer());
node.addChildToBack(initalizer);
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.rhino.head.Token.typeToName(
node.getType()),
sourceName,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>LSH;
case com.google.javascript.rhino.head.Token.RSH:
return Token.RSH;
case com.google.javascript.rhino.head.Token.URSH:
return Token.URSH;
case com.google.javascript.rhino.head.Token.ADD:
return Token.ADD;
case com.google.javascript.rhino.head.Token.SUB:
return Token.SUB;
case com.google.javascript.rhino.head.Token.MUL:
return Token.MUL;
case com.google.javascript.rhino.head.Token.DIV:
return Token.DIV;
case com.google.javascript.rhino.head.Token.MOD:
return Token.MOD;
case com.google.javascript.rhino.head.Token.NOT:
return Token.NOT;
case com.google.javascript.rhino.head.Token.BITNOT:
return Token.BITNOT;
case com.google.javascript.rhino.head.Token.POS:
return Token.POS;
case com.google.javascript.rhino.head.Token.NEG:
return Token.NEG;
case com.google.javascript.rhino.head.Token.NEW:
return Token.NEW;
case com.google.javascript.rhino.head.Token.DELPROP:
return Token.DELPROP;
case com.google.javascript.rhino.head.Token.TYPEOF:
return Token.TYPEOF;
case com.google.javascript.rhino.head.Token.GETPROP:
return Token.GETPROP;
case com.google.javascript.rhino.head.Token.GETELEM:
return Token.GETELEM;
case com.google.javascript.rhino.head.Token.CALL:
return Token.CALL;
case com.google.javascript.rhino.head.Token.NAME:
return Token.NAME;
case com.google.javascript.rhino.head.Token.NUMBER:
return Token.NUMBER;
case com.google.javascript.rhino.head.Token.STRING:
return Token.STRING;
case com.google.javascript.rhino.head.Token.NULL:
return Token.NULL;
case com.google.javascript.rh
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.ErrorReporter;
/**
* Value types (null, void, number, boolean, string).
*/
abstract class ValueType extends JSType {
ValueType(JSTypeRegistry registry) {
super(registry);
}
@Override
final JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
@Override
public
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.getGrandparent();
if (grandparent.isName()
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>toObjectType();
FunctionType thatCtor = thatObj == null ? null : thatObj.getConstructor();
if (getConstructor() != null && getConstructor().isInterface()) {
for (ObjectType thisInterface : getCtorExtendedInterfaces()) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
} else if (thatCtor != null && thatCtor.isInterface()) {
Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces();
for (ObjectType thisInterface : thisInterfaces) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
// other prototype based objects
if (isUnknownType() || implicitPrototypeChainIsUnknown()) {
// If unsure, say 'yes', to avoid spurious warnings.
// TODO(user): resolve the prototype chain completely in all cases,
// to avoid guessing.
return true;
}
return thatObj != null && isImplicitPrototype(thatObj);
}
private boolean implicitPrototypeChainIsUnknown() {
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.isUnknownType()) {
return true;
}
p = p.getImplicitPrototype();
}
return false;
}
@Override
public boolean hasCachedValues() {
return super.hasCachedValues();
}
/** Whether this is a built-in object. */
@Override
public boolean isNativeObjectType() {
return nativeType;
}
@Override
void setOwnerFunction(FunctionType type) {
Preconditions.checkState(ownerFunction == null || type == null);
ownerFunction = type;
}
@Override
public FunctionType getOwnerFunction() {
return ownerFunction;
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getImplementedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
public Iterable<ObjectType> getCtorExtendedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getExtendedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype !=
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> null) {
implicitPrototypeFallback =
(ObjectType) implicitPrototype.resolve(t, scope);
}
for (Property prop : properties.values()) {
prop.setType(safeResolve(prop.getType(), t, scope));
}
return this;
}
@Override
public void matchConstraint(JSType constraint) {
// We only want to match constraints on anonymous types.
if (hasReferenceName()) {
return;
}
// Handle the case where the constraint object is a record type.
//
// param constraint {{prop: (number|undefined)}}
// function f(constraint) {}
// f({});
//
// We want to modify the object literal to match the constraint, by
// taking any each property on the record and trying to match
// properties on this object.
if (constraint.isRecordType()) {
matchRecordTypeConstraint(constraint.toObjectType());
} else if (constraint.isUnionType()) {
for (JSType alt : constraint.toMaybeUnionType().getAlternates()) {
if (alt.isRecordType()) {
matchRecordTypeConstraint(alt.toObjectType());
}
}
}
}
public void matchRecordTypeConstraint(ObjectType constraintObj) {
for (String prop : constraintObj.getOwnPropertyNames()) {
JSType propType = constraintObj.getPropertyType(prop);
if (!isPropertyTypeDeclared(prop)) {
JSType typeToInfer = propType;
if (!hasProperty(prop)) {
typeToInfer = getNativeType(JSTypeNative.VOID_TYPE)
.getLeastSupertype(propType);
}
defineInferredProperty(prop, typeToInfer, null);
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Scopes();
symbolTable.fillThisReferences(this, externsRoot, jsRoot);
symbolTable.fillPropertySymbols(this, externsRoot, jsRoot);
symbolTable.fillJSDocInfo(this, externsRoot, jsRoot);
return symbolTable;
}
@Override
public Scope getTopScope() {
return getPassConfig().getTopScope();
}
@Override
public ReverseAbstractInterpreter getReverseAbstractInterpreter() {
if (abstractInterpreter == null) {
ChainableReverseAbstractInterpreter interpreter =
new SemanticReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry());
if (options.closurePass) {
interpreter = new ClosureReverseAbstractInterpreter(
getCodingConvention(), getTypeRegistry())
.append(interpreter).getFirst();
}
abstractInterpreter = interpreter;
}
return abstractInterpreter;
}
@Override
TypeValidator getTypeValidator() {
if (typeValidator == null) {
typeValidator = new TypeValidator(this);
}
return typeValidator;
}
//------------------------------------------------------------------------
// Parsing
//------------------------------------------------------------------------
/**
* Parses the externs and main inputs.
*
* @return A synthetic root node whose two children are the externs root
* and the main root
*/
Node parseInputs() {
boolean devMode = options.devMode != DevMode.OFF;
// If old roots exist (we are parsing a second time), detach each of the
// individual file parse trees.
if (externsRoot != null) {
externsRoot.detachChildren();
}
if (jsRoot != null) {
jsRoot.detachChildren();
}
// Parse main JS sources.
jsRoot = IR.block();
jsRoot.setIsSyntheticBlock(true);
externsRoot = IR.block();
externsRoot.setIsSyntheticBlock(true);
externAndJsRoot = IR.block(externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
if (options.tracer.isOn()) {
tracker = new PerformanceTracker(jsRoot, options.tracer);
addChangeHandler(tracker.getCodeChangeHandler());
}
Tracer tracer = newTracer("parseInputs");
try {
// Parse externs sources.
for (CompilerInput input : externs) {
Node n = input.getAst
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(firstChild);
} else {
parent.addChildrenAfter(
firstChild, injectedLibraries.get("base"));
}
reportCodeChange();
injectedLibraries.put(resourceName, lastChild);
return lastChild;
}
/** Load a library as a resource */
@VisibleForTesting
Node loadLibraryCode(String resourceName) {
String originalCode;
try {
originalCode = CharStreams.toString(new InputStreamReader(
Compiler.class.getResourceAsStream(
String.format("js/%s.js", resourceName)),
Charsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
return Normalize.parseAndNormalizeSyntheticCode(
this, originalCode,
String.format("jscomp_%s_", resourceName));
}
/** Returns the compiler version baked into the jar. */
public static String getReleaseVersion() {
ResourceBundle config = ResourceBundle.getBundle(CONFIG_RESOURCE);
return config.getString("compiler.version");
}
/** Returns the compiler date baked into the jar. */
public static String getReleaseDate() {
ResourceBundle config = ResourceBundle.getBundle(CONFIG_RESOURCE);
return config.getString("compiler.date");
}
/**
* Stores the old parse tree for a given source file.
* @param sourceName
* @param oldAst
*/
public void setOldParseTree(String sourceName, AstRoot oldAst) {
}
/**
* Gets an old format parse tree for a given source file.
* @param sourceName
* @return
*/
public AstRoot getOldParseTreeByName(String sourceName) {
return null;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> return getPropertyMap().getSlot(name);
}
@Override
public Property getOwnSlot(String name) {
return getPropertyMap().getOwnProperty(name);
}
@Override
public JSType getTypeOfThis() {
return null;
}
/**
* Gets the declared default element type.
* @see ParameterizedType
*/
public JSType getParameterType() {
return null;
}
/**
* Gets the declared default index type.
* @see IndexedType
*/
public JSType getIndexType() {
return null;
}
/**
* Gets the docInfo for this type.
*/
@Override
public JSDocInfo getJSDocInfo() {
if (docInfo != null) {
return docInfo;
} else if (getImplicitPrototype() != null) {
return getImplicitPrototype().getJSDocInfo();
} else {
return super.getJSDocInfo();
}
}
/**
* Sets the docInfo for this type from the given
* {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}.
*/
public void setJSDocInfo(JSDocInfo info) {
docInfo = info;
}
/**
* Detects a cycle in the implicit prototype chain. This method accesses
* the {@link #getImplicitPrototype()} method and must therefore be
* invoked only after the object is sufficiently initialized to respond to
* calls to this method.<p>
*
* @return True iff an implicit prototype cycle was detected.
*/
final boolean detectImplicitPrototypeCycle() {
// detecting cycle
this.visited = true;
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.visited) {
return true;
} else {
p.visited = true;
}
p = p.getImplicitPrototype();
}
// clean up
p = this;
do {
p.visited = false;
p = p.getImplicitPrototype();
} while (p != null);
return false;
}
/**
* Detects cycles in either the implicit prototype chain, or the implemented/extended
* interfaces.<p>
*
* @return True iff a cycle was detected.
*/
final boolean detectInheritanceCycle() {
//
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> getPropertyNode(String propertyName) {
Property p = getSlot(propertyName);
return p == null ? null : p.getNode();
}
/**
* Gets the docInfo on the specified property on this type. This should not
* be implemented recursively, as you generally need to know exactly on
* which type in the prototype chain the JSDocInfo exists.
*/
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
Property p = getOwnSlot(propertyName);
return p == null ? null : p.getJSDocInfo();
}
/**
* Sets the docInfo for the specified property from the
* {@link JSDocInfo} on its definition.
* @param info {@code JSDocInfo} for the property definition. May be
* {@code null}.
*/
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
// by default, do nothing
}
@Override
public JSType findPropertyType(String propertyName) {
return hasProperty(propertyName) ?
getPropertyType(propertyName) : null;
}
/**
* Gets the property type of the property whose name is given. If the
* underlying object does not have this property, the Unknown type is
* returned to indicate that no information is available on this property.
*
* This gets overridden by FunctionType for lazily-resolved call() and
* bind() functions.
*
* @return the property's type or {@link UnknownType}. This method never
* returns {@code null}.
*/
public JSType getPropertyType(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
if (slot == null) {
if (isNoResolvedType() || isCheckedUnknownType()) {
return getNativeType(JSTypeNative.CHECKED_UNKNOWN_TYPE);
} else if (isEmptyType()) {
return getNativeType(JSTypeNative.NO_TYPE);
}
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return slot.getType();
}
@Override
public boolean hasProperty(String propertyName) {
// Unknown types have all properties.
return isEmptyType() || isUnknownType() || getSlot(propertyName) != null;
}
/**
* Checks whether the property whose name
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> is given is present directly on
* the object. Returns false even if it is declared on a supertype.
*/
public boolean hasOwnProperty(String propertyName) {
return getOwnSlot(propertyName) != null;
}
/**
* Returns the names of all the properties directly on this type.
*
* Overridden by FunctionType to add "prototype".
*/
public Set<String> getOwnPropertyNames() {
return getPropertyMap().getOwnPropertyNames();
}
/**
* Checks whether the property's type is inferred.
*/
public boolean isPropertyTypeInferred(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
return slot == null ? false : slot.isTypeInferred();
}
/**
* Checks whether the property's type is declared.
*/
public boolean isPropertyTypeDeclared(String propertyName) {
StaticSlot<JSType> slot = getSlot(propertyName);
return slot == null ? false : !slot.isTypeInferred();
}
/**
* Whether the given property is declared on this object.
*/
final boolean hasOwnDeclaredProperty(String name) {
return hasOwnProperty(name) && isPropertyTypeDeclared(name);
}
/** Checks whether the property was defined in the externs. */
public boolean isPropertyInExterns(String propertyName) {
Property p = getSlot(propertyName);
return p == null ? false : p.isFromExterns();
}
/**
* Gets the number of properties of this object.
*/
public int getPropertiesCount() {
return getPropertyMap().getPropertiesCount();
}
/**
* Returns a list of properties defined or inferred on this type and any of
* its supertypes.
*/
public Set<String> getPropertyNames() {
Set<String> props = Sets.newTreeSet();
collectPropertyNames(props);
return props;
}
/**
* Adds any properties defined on this type or its supertypes to the set.
*/
final void collectPropertyNames(Set<String> props) {
getPropertyMap().collectPropertyNames(props);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseObjectType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> Token.THIS:
case Token.TRUE:
validateChildless(n);
return;
// General unary ops
case Token.DELPROP:
case Token.POS:
case Token.NEG:
case Token.NOT:
case Token.INC:
case Token.DEC:
case Token.TYPEOF:
case Token.VOID:
case Token.BITNOT:
case Token.CAST:
validateUnaryOp(n);
return;
// General binary ops
case Token.COMMA:
case Token.OR:
case Token.AND:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.ADD:
case Token.MUL:
case Token.MOD:
case Token.DIV:
validateBinaryOp(n);
return;
// Assignments
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
validateAssignmentExpression(n);
return;
case Token.HOOK:
validateTrinaryOp(n);
return;
// Node types that require special handling
case Token.STRING:
validateString(n);
return;
case Token.NUMBER:
validateNumber(n);
return;
case Token.NAME:
validateName(n);
return;
case Token.GETELEM:
validateBinaryOp(n);
return;
case Token.GETPROP:
validateGetProp(n);
return;
case Token.ARRAYLIT:
validateArrayLit(n);
return;
case Token.OBJECTLIT:
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> validateObjectLit(n);
return;
case Token.REGEXP:
validateRegExpLit(n);
return;
case Token.CALL:
validateCall(n);
return;
case Token.NEW:
validateNew(n);
return;
case Token.FUNCTION:
validateFunctionExpression(n);
return;
default:
violation("Expected expression but was "
+ Token.name(n.getType()), n);
}
}
private void validateBlock(Node n) {
validateNodeType(Token.BLOCK, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateStatement(c);
}
}
private void validateSyntheticBlock(Node n) {
validateNodeType(Token.BLOCK, n);
validateIsSynthetic(n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateStatement(c);
}
}
private void validateIsSynthetic(Node n) {
if (!n.getBooleanProp(Node.SYNTHETIC_BLOCK_PROP)) {
violation("Missing 'synthetic block' annotation.", n);
}
}
private void validateHasSourceName(Node n) {
String sourceName = n.getSourceFileName();
if (sourceName == null || sourceName.isEmpty()) {
violation("Missing 'source name' annotation.", n);
}
}
private void validateHasInputId(Node n) {
InputId inputId = n.getInputId();
if (inputId == null) {
violation("Missing 'input id' annotation.", n);
}
}
private void validateLabel(Node n) {
validateNodeType(Token.LABEL, n);
validateChildCount(n, 2);
validateLabelName(n.getFirstChild());
validateStatement(n.getLastChild());
}
private void validateLabelName(Node n) {
validateNodeType(Token.LABEL_NAME, n);
validateNonEmptyString(n);
validateChildCount(n, 0);
}
private void validateNonEmptyString(Node n) {
validateNonNullString(n);
if (n.getString().isEmpty()) {
violation("Expected non-empty string.", n);
}
}
private void validateNonNullString(Node n) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
if (n.getString() == null) {
violation("Expected non-null string.", n);
}
}
private void validateName(Node n) {
validateNodeType(Token.NAME, n);
validateNonEmptyString(n);
validateChildCount(n, 0);
}
private void validateOptionalName(Node n) {
validateNodeType(Token.NAME, n);
validateNonNullString(n);
validateChildCount(n, 0);
}
private void validateFunctionStatement(Node n) {
validateNodeType(Token.FUNCTION, n);
validateChildCount(n, 3);
validateName(n.getFirstChild());
validateParameters(n.getChildAtIndex(1));
validateBlock(n.getLastChild());
}
private void validateFunctionExpression(Node n) {
validateNodeType(Token.FUNCTION, n);
validateChildCount(n, 3);
validateOptionalName(n.getFirstChild());
validateParameters(n.getChildAtIndex(1));
validateBlock(n.getLastChild());
}
private void validateParameters(Node n) {
validateNodeType(Token.PARAM_LIST, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateName(c);
}
}
private void validateCall(Node n) {
validateNodeType(Token.CALL, n);
validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateExpression(c);
}
}
private void validateNew(Node n) {
validateNodeType(Token.NEW, n);
validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateExpression(c);
}
}
private void validateVar(Node n) {
validateNodeType(Token.VAR, n);
this.validateMinimumChildCount(n, 1);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
// Don't use the validateName here as the NAME is allowed to have
// a child.
validateNodeType(Token.NAME, c);
validateNonEmptyString(c);
validateMaximumChildCount(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(n.getLastChild());
}
private void validateSwitch(Node n) {
validateNodeType(Token.SWITCH, n);
validateMinimumChildCount(n, 1);
validateExpression(n.getFirstChild());
int defaults = 0;
for (Node c = n.getFirstChild().getNext(); c != null; c = c.getNext()) {
validateSwitchMember(n.getLastChild());
if (c.isDefaultCase()) {
defaults++;
}
}
if (defaults > 1) {
violation("Expected at most 1 'default' in switch but was "
+ defaults, n);
}
}
private void validateSwitchMember(Node n) {
switch (n.getType()) {
case Token.CASE:
validateCase(n);
return;
case Token.DEFAULT_CASE:
validateDefault(n);
return;
default:
violation("Expected switch member but was "
+ Token.name(n.getType()), n);
}
}
private void validateDefault(Node n) {
validateNodeType(Token.DEFAULT_CASE, n);
validateChildCount(n, 1);
validateSyntheticBlock(n.getLastChild());
}
private void validateCase(Node n) {
validateNodeType(Token.CASE, n);
validateChildCount(n, 2);
validateExpression(n.getFirstChild());
validateSyntheticBlock(n.getLastChild());
}
private void validateOptionalExpression(Node n) {
if (n.isEmpty()) {
validateChildless(n);
} else {
validateExpression(n);
}
}
private void validateChildless(Node n) {
validateChildCount(n, 0);
}
private void validateAssignmentExpression(Node n) {
validateChildCount(n, 2);
validateAssignmentTarget(n.getFirstChild());
validateExpression(n.getLastChild());
}
private void validateAssignmentTarget(Node n) {
switch (n.getType()) {
case Token.NAME:
case Token.GETELEM:
case Token.GETPROP:
validateExpression(n);
return;
default:
violation("Expected assignment target expression but was "
+ Token.name(n.getType()), n);
}
}
private void validateGetProp(Node n)
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {
validateNodeType(Token.GETPROP, n);
validateChildCount(n, 2);
validateExpression(n.getFirstChild());
Node prop = n.getLastChild();
validateNodeType(Token.STRING, prop);
validateNonEmptyString(prop);
}
private void validateRegExpLit(Node n) {
validateNodeType(Token.REGEXP, n);
validateMinimumChildCount(n, 1);
validateMaximumChildCount(n, 2);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateString(c);
}
}
private void validateString(Node n) {
validateNodeType(Token.STRING, n);
validateChildCount(n, 0);
try {
// Validate that getString doesn't throw
n.getString();
} catch (UnsupportedOperationException e) {
violation("Invalid STRING node.", n);
}
}
private void validateNumber(Node n) {
validateNodeType(Token.NUMBER, n);
validateChildCount(n, 0);
try {
// Validate that getDouble doesn't throw
n.getDouble();
} catch (UnsupportedOperationException e) {
violation("Invalid NUMBER node.", n);
}
}
private void validateArrayLit(Node n) {
validateNodeType(Token.ARRAYLIT, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
// EMPTY is allowed to represent empty slots.
validateOptionalExpression(c);
}
}
private void validateObjectLit(Node n) {
validateNodeType(Token.OBJECTLIT, n);
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
validateObjectLitKey(c);
}
}
private void validateObjectLitKey(Node n) {
switch (n.getType()) {
case Token.GETTER_DEF:
validateObjectLitGetKey(n);
return;
case Token.SETTER_DEF:
validateObjectLitSetKey(n);
return;
case Token.STRING_KEY:
validateObjectLitStringKey(n);
return;
default:
violation("Expected object literal key expression but was "
+ Token.name(n.getType()), n);
}
}
private void validateObject
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>LitGetKey(Node n) {
validateNodeType(Token.GETTER_DEF, n);
validateChildCount(n, 1);
validateObjectLiteralKeyName(n);
Node function = n.getFirstChild();
validateFunctionExpression(function);
// objlit get functions must be nameless, and must have zero parameters.
if (!function.getFirstChild().getString().isEmpty()) {
violation("Expected unnamed function expression.", n);
}
Node functionParams = function.getChildAtIndex(1);
if (functionParams.hasChildren()) {
violation("get methods must not have parameters.", n);
}
}
private void validateObjectLitSetKey(Node n) {
validateNodeType(Token.SETTER_DEF, n);
validateChildCount(n, 1);
validateObjectLiteralKeyName(n);
Node function = n.getFirstChild();
validateFunctionExpression(function);
// objlit set functions must be nameless, and must have 1 parameter.
if (!function.getFirstChild().getString().isEmpty()) {
violation("Expected unnamed function expression.", n);
}
Node functionParams = function.getChildAtIndex(1);
if (!functionParams.hasOneChild()) {
violation("set methods must have exactly one parameter.", n);
}
}
private void validateObjectLitStringKey(Node n) {
validateNodeType(Token.STRING_KEY, n);
validateChildCount(n, 1);
validateObjectLiteralKeyName(n);
validateExpression(n.getFirstChild());
}
private void validateObjectLiteralKeyName(Node n) {
if (n.isQuotedString()) {
try {
// Validate that getString doesn't throw
n.getString();
} catch (UnsupportedOperationException e) {
violation("getString failed for" + Token.name(n.getType()), n);
}
} else {
validateNonEmptyString(n);
}
}
private void validateUnaryOp(Node n) {
validateChildCount(n, 1);
validateExpression(n.getFirstChild());
}
private void validateBinaryOp(Node n) {
validateChildCount(n, 2);
validateExpression(n.getFirstChild());
validateExpression(n.getLastChild());
}
private void validateTrinaryOp(Node n) {
validateChildCount(n, 3);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private InputId inputId;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
Abstract
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Compiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = Scope.createGlobalScope(n);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.isFunction()) {
if (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().contains("duplicate"));
}
/**
* Generates an untyped global scope from the root
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> of AST of compiler (which
* includes externs).
*
* @param compiler The compiler for which the scope is generated.
* @return The new untyped global scope generated as a result of this call.
*/
static Scope generateUntypedTopScope(AbstractCompiler compiler) {
return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(),
null);
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in JavaScript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !n.isName()
|| parent.isFunction()) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().isCatch() ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!noVarCheck) {
t.report(n, UNKNOWN_VARIABLE, n.getString());
}
}
if (!noCajaChecks) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_USE);
} else if (n.getString().endsWith("__")) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks that an assignment is not to the "arguments" object. */
private void checkAssignment(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
if ("arguments".equals(n.getFirstChild().getString())) {
t.report(n, ARGUMENTS_ASSIGNMENT);
} else if ("eval".equals(n.getFirstChild().getString())) {
// Note that assignment to eval is already illegal because any use of
// that name is illegal.
if (noCajaChecks) {
t.report(n, EVAL_ASSIGNMENT);
}
}
}
}
/** Checks that variables, functions, and arguments are not deleted. */
private void checkDelete(NodeTraversal t, Node n) {
if (n.getFirstChild().isName()) {
Var v = t.getScope().getVar(n.getFirstChild().getString());
if (v != null) {
t.report(n, DELETE_VARIABLE);
}
}
}
/** Checks that object literal keys are valid. */
private void checkObjectLiteral(NodeTraversal t, Node n) {
Set
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS><String> getters = Sets.newHashSet();
Set<String> setters = Sets.newHashSet();
for (Node key = n.getFirstChild();
key != null;
key = key.getNext()) {
if (!noCajaChecks && key.getString().endsWith("__")) {
t.report(key, ILLEGAL_NAME);
}
if (!key.isSetterDef()) {
// normal property and getter cases
if (getters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
getters.add(key.getString());
}
}
if (!key.isGetterDef()) {
// normal property and setter cases
if (setters.contains(key.getString())) {
t.report(key, DUPLICATE_OBJECT_KEY);
} else {
setters.add(key.getString());
}
}
}
}
/** Checks that label names are valid. */
private void checkLabel(NodeTraversal t, Node n) {
if (n.getFirstChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getFirstChild(), ILLEGAL_NAME);
}
}
}
/** Checks that are performed on non-extern code only. */
private class NonExternChecks extends AbstractPostOrderCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if ((n.isName()) && isDeclaration(n)) {
checkDeclaration(t, n);
} else if (n.isGetProp()) {
checkProperty(t, n);
}
}
/** Checks for illegal declarations. */
private void checkDeclaration(NodeTraversal t, Node n) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_DECLARATION);
} else if ("arguments".equals(n.getString())) {
t.report(n, ARGUMENTS_DECLARATION);
} else if (n.getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks for illegal property accesses. */
private void checkProperty(NodeTraversal t, Node n
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) {
if (n.getLastChild().getString().endsWith("__")) {
if (!noCajaChecks) {
t.report(n.getLastChild(), ILLEGAL_NAME);
}
}
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>can.c.
*/
public final static int
ERROR = -1,
RETURN = 4,
BITOR = 9,
BITXOR = 10,
BITAND = 11,
EQ = 12,
NE = 13,
LT = 14,
LE = 15,
GT = 16,
GE = 17,
LSH = 18,
RSH = 19,
URSH = 20,
ADD = 21,
SUB = 22,
MUL = 23,
DIV = 24,
MOD = 25,
NOT = 26,
BITNOT = 27,
POS = 28,
NEG = 29,
NEW = 30,
DELPROP = 31,
TYPEOF = 32,
GETPROP = 33,
GETELEM = 35,
CALL = 37,
NAME = 38,
NUMBER = 39,
STRING = 40,
NULL = 41,
THIS = 42,
FALSE = 43,
TRUE = 44,
SHEQ = 45, // shallow equality (===)
SHNE = 46, // shallow inequality (!==)
REGEXP = 47,
THROW = 49,
IN = 51,
INSTANCEOF = 52,
ARRAYLIT = 63, // array literal
OBJECTLIT = 64, // object literal
TRY = 77,
PARAM_LIST = 83,
COMMA = 85, // comma operator
ASSIGN = 86, // simple assignment (=)
ASSIGN_BITOR = 87, // |=
ASSIGN_BITXOR = 88, // ^=
ASSIGN_BITAND = 89, // &=
ASSIGN_LSH = 90, // <<=
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>SDoc-only tokens
ANNOTATION = 300,
PIPE = 301,
STAR = 302,
EOC = 303,
QMARK = 304,
ELLIPSIS = 305,
BANG = 306,
EQUALS = 307,
LB = 308, // left brackets
LC = 309, // left curly braces
COLON = 310;
// Transitional definitions
// TODO(johnlenz): remove these
public final static int
DEFAULT = DEFAULT_CASE,
GET = GETTER_DEF,
LP = PARAM_LIST,
SET = SETTER_DEF;
public static String name(int token) {
switch (token) {
case ERROR: return "ERROR";
case RETURN: return "RETURN";
case BITOR: return "BITOR";
case BITXOR: return "BITXOR";
case BITAND: return "BITAND";
case EQ: return "EQ";
case NE: return "NE";
case LT: return "LT";
case LE: return "LE";
case GT: return "GT";
case GE: return "GE";
case LSH: return "LSH";
case RSH: return "RSH";
case URSH: return "URSH";
case ADD: return "ADD";
case SUB: return "SUB";
case MUL: return "MUL";
case DIV: return "DIV";
case MOD: return "MOD";
case NOT: return "NOT";
case BITNOT: return "BITNOT";
case POS: return "POS";
case NEG: return "NEG";
case NEW: return "NEW";
case DELPROP: return "DELPROP";
case TYPEOF: return "TYPEOF";
case GETPROP: return "GETPROP";
case GETELEM: return "GETELEM";
case CALL: return "CALL";
case NAME: return "NAME";
case LABEL_NAME: return "LABEL_NAME";
case NUMBER: return
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>URE)
? CheckLevel.ERROR : CheckLevel.WARNING;
// TODO(anatol): add flag that decides whether to process UNNAMED messages.
// Some projects would not want such functionality (unnamed) as they don't
// use SOY templates.
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) {
compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(),
checkLevel, MESSAGE_NODE_IS_ORPHANED));
}
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
String messageKey;
boolean isVar;
Node msgNode, msgNodeParent;
switch (node.getType()) {
case Token.NAME:
// var MSG_HELLO = 'Message'
if ((parent != null) && (parent.isVar())) {
messageKey = node.getString();
isVar = true;
} else {
return;
}
msgNode = node.getFirstChild();
msgNodeParent = node;
break;
case Token.ASSIGN:
// somenamespace.someclass.MSG_HELLO = 'Message'
isVar = false;
Node getProp = node.getFirstChild();
if (!getProp.isGetProp()) {
return;
}
Node propNode = getProp.getLastChild();
messageKey = propNode.getString();
msgNode = node.getLastChild();
msgNodeParent = node;
break;
case Token.CALL:
// goog.getMsg()
String fnName = node.getFirstChild().getQualifiedName();
if (MSG_FUNCTION_NAME.equals(fnName)) {
googMsgNodes.put(node, traversal.getSourceName());
} else if (MSG_FALLBACK_FUNCTION_NAME.equals(fnName)) {
visitFallbackFunctionCall(traversal, node);
}
return;
default:
return;
}
// Is this a message name?
boolean isNewStyleMessage =
msgNode != null && msgNode.isCall();
if (!isMessageName(messageKey, isNewStyleMessage)) {
return;
}
if
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(traversal.makeError(node, MESSAGE_HAS_NO_DESCRIPTION,
messageKey));
}
JsMessageDefinition msgDefinition = new JsMessageDefinition(
node, msgNode, msgNodeParent);
processJsMessage(extractedMessage, msgDefinition);
}
/**
* Track a message for later retrieval.
*
* This is used for tracking duplicates, and for figuring out message
* fallback. Not all message types are trackable, because that would
* require a more sophisticated analysis. e.g.,
* function f(s) { s.MSG_UNNAMED_X = 'Some untrackable message'; }
*/
private void trackMessage(
NodeTraversal t, JsMessage message, String msgName,
Node msgNode, boolean isUnnamedMessage) {
if (!isUnnamedMessage) {
MessageLocation location = new MessageLocation(message, msgNode);
messageNames.put(msgName, location);
} else if (msgNode.isName()) {
Var var = t.getScope().getVar(msgName);
if (var != null) {
unnamedMessages.put(var, message);
}
}
}
/** Get a previously tracked message. */
private JsMessage getTrackedMessage(NodeTraversal t, String msgName) {
boolean isUnnamedMessage = isUnnamedMessageName(msgName);
if (!isUnnamedMessage) {
MessageLocation location = messageNames.get(msgName);
return location == null ? null : location.message;
} else {
Var var = t.getScope().getVar(msgName);
if (var != null) {
return unnamedMessages.get(var);
}
}
return null;
}
/**
* Checks if message already processed. If so - it generates 'message
* duplicated' compiler error.
*
* @param msgName the name of the message
* @param msgNode the node that represents JS message
*/
private void checkIfMessageDuplicated(String msgName, Node msgNode) {
if (messageNames.containsKey(msgName)) {
MessageLocation location = messageNames.get(msgName);
compiler.report(JSError.make(msgNode, MESSAGE_DUPLICATE_KEY,
msgName, location.messageNode.getSourceFileName(),
Integer.toString(location.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> correspond to a valid JS message node
*/
private void extractMessageFromProperty(
Builder builder, Node getPropNode, Node assignNode)
throws MalformedException {
Node callNode = getPropNode.getNext();
maybeInitMetaDataFromJsDoc(builder, assignNode);
extractFromCallNode(builder, callNode);
}
/**
* Initializes the meta data in a JsMessage by examining the nodes just before
* and after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param varNode the message VAR node
* @param parentOfVarNode {@code varNode}'s parent node
*/
private void maybeInitMetaDataFromJsDocOrHelpVar(
Builder builder, Node varNode, @Nullable Node parentOfVarNode)
throws MalformedException {
// First check description in @desc
if (maybeInitMetaDataFromJsDoc(builder, varNode)) {
return;
}
// Check the preceding node for meta data
if ((parentOfVarNode != null) &&
maybeInitMetaDataFromHelpVar(builder,
parentOfVarNode.getChildBefore(varNode))) {
return;
}
// Check the subsequent node for meta data
maybeInitMetaDataFromHelpVar(builder, varNode.getNext());
}
/**
* Initializes the meta data in a JsMessage by examining a node just before or
* after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param sibling a node adjacent to the message VAR node
* @return true iff message has corresponding description variable
*/
private boolean maybeInitMetaDataFromHelpVar(Builder builder,
@Nullable Node sibling) throws MalformedException {
if ((sibling != null) && (sibling.isVar())) {
Node nameNode = sibling.getFirstChild();
String name = nameNode.getString();
if (name.equals(builder.getKey() + DESC_SUFFIX)) {
Node valueNode = nameNode.getFirstChild();
String desc = extractStringFromStringExprNode(valueNode);
if (desc.startsWith(HIDDEN_DESC_PREFIX)) {
builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim());
builder.setIsHidden(true);
} else {
builder.setDesc(desc);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
return true;
}
}
return false;
}
/**
* Initializes the meta data in a message builder given a node that may
* contain JsDoc properties.
*
* @param builder the message builder whose meta data will be initialized
* @param node the node with the message's JSDoc properties
* @return true if message has JsDoc with valid description in @desc
* annotation
*/
private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) {
boolean messageHasDesc = false;
JSDocInfo info = node.getJSDocInfo();
if (info != null) {
String desc = info.getDescription();
if (desc != null) {
builder.setDesc(desc);
messageHasDesc = true;
}
if (info.isHidden()) {
builder.setIsHidden(true);
}
if (info.getMeaning() != null) {
builder.setMeaning(info.getMeaning());
}
}
return messageHasDesc;
}
/**
* Returns the string value associated with a node representing a JS string or
* several JS strings added together (e.g. {@code 'str'} or {@code 's' + 't' +
* 'r'}).
*
* @param node the node from where we extract the string
* @return String representation of the node
* @throws MalformedException if the parsed message is invalid
*/
private static String extractStringFromStringExprNode(Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
return node.getString();
case Token.ADD:
StringBuilder sb = new StringBuilder();
for (Node child : node.children()) {
sb.append(extractStringFromStringExprNode(child));
}
return sb.toString();
default:
throw new MalformedException("STRING or ADD node expected; found: " +
getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a FUNCTION node.
* <p>
* <pre>
* The tree should look something like:
*
* function
* |-- name
* |-- lp
* | |-- name <arg1>
* | -- name <arg2>
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> * -- block
* |
* --return
* |
* --add
* |-- string foo
* -- name <arg1>
* </pre>
*
* @param builder the message builder
* @param node the function node that contains a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromFunctionNode(Builder builder, Node node)
throws MalformedException {
Set<String> phNames = Sets.newHashSet();
for (Node fnChild : node.children()) {
switch (fnChild.getType()) {
case Token.NAME:
// This is okay. The function has a name, but it is empty.
break;
case Token.PARAM_LIST:
// Parse the placeholder names from the function argument list.
for (Node argumentNode : fnChild.children()) {
if (argumentNode.isName()) {
String phName = argumentNode.getString();
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, argumentNode);
} else {
phNames.add(phName);
}
}
}
break;
case Token.BLOCK:
// Build the message's value by examining the return statement
Node returnNode = fnChild.getFirstChild();
if (!returnNode.isReturn()) {
throw new MalformedException("RETURN node expected; found: "
+ getReadableTokenName(returnNode), returnNode);
}
for (Node child : returnNode.children()) {
extractFromReturnDescendant(builder, child);
}
// Check that all placeholders from the message text have appropriate
// object literal keys
for (String phName : builder.getPlaceholders()) {
if(!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
returnNode);
}
}
break;
default:
throw new MalformedException(
"NAME, LP, or BLOCK node expected; found: "
+ getReadableTokenName(node), fnChild);
}
}
}
/**
* Appends value parts to the message builder by traversing the descendants
* of the given RETURN node.
*
* @param
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> builder the message builder
* @param node the node from where we extract a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromReturnDescendant(Builder builder, Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
builder.appendStringPart(node.getString());
break;
case Token.NAME:
builder.appendPlaceholderReference(node.getString());
break;
case Token.ADD:
for (Node child : node.children()) {
extractFromReturnDescendant(builder, child);
}
break;
default:
throw new MalformedException(
"STRING, NAME, or ADD node expected; found: "
+ getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a CALL node.
* <p>
* The tree should look something like:
*
* <pre>
* call
* |-- getprop
* | |-- name 'goog'
* | +-- string 'getMsg'
* |
* |-- string 'Hi {$userName}! Welcome to {$product}.'
* +-- objlit
* |-- string 'userName'
* |-- name 'someUserName'
* |-- string 'product'
* +-- call
* +-- name 'getProductName'
* </pre>
*
* @param builder the message builder
* @param node the call node from where we extract the message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromCallNode(Builder builder,
Node node) throws MalformedException {
// Check the function being called
if (!node.isCall()) {
throw new MalformedException(
"Message must be initialized using " + MSG_FUNCTION_NAME +
" function.", node);
}
Node fnNameNode = node.getFirstChild();
if (!MSG_FUNCTION_NAME.equals(fnNameNode.getQualifiedName())) {
throw new MalformedException(
"Message initialized using unrecognized function. " +
"Please use " + MSG_FUNCTION_NAME + "() instead.", fnNameNode);
}
// Get the message string
Node stringLiteralNode = fnNameNode.getNext();
if (stringLiteral
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Node == null) {
throw new MalformedException("Message string literal expected",
stringLiteralNode);
}
// Parse the message string and append parts to the builder
parseMessageTextNode(builder, stringLiteralNode);
Node objLitNode = stringLiteralNode.getNext();
Set<String> phNames = Sets.newHashSet();
if (objLitNode != null) {
// Register the placeholder names
if (!objLitNode.isObjectLit()) {
throw new MalformedException("OBJLIT node expected", objLitNode);
}
for (Node aNode = objLitNode.getFirstChild(); aNode != null;
aNode = aNode.getNext()) {
if (!aNode.isStringKey()) {
throw new MalformedException("STRING_KEY node expected as OBJLIT key",
aNode);
}
String phName = aNode.getString();
if (!isLowerCamelCaseWithNumericSuffixes(phName)) {
throw new MalformedException(
"Placeholder name not in lowerCamelCase: " + phName, aNode);
}
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, aNode);
}
phNames.add(phName);
}
}
// Check that all placeholders from the message text have appropriate objlit
// values
Set<String> usedPlaceholders = builder.getPlaceholders();
for (String phName : usedPlaceholders) {
if(!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
objLitNode);
}
}
// Check that objLiteral have only names that are present in the
// message text
for (String phName : phNames) {
if(!usedPlaceholders.contains(phName)) {
throw new MalformedException(
"Unused message placeholder: " + phName,
objLitNode);
}
}
}
/**
* Appends the message parts in a JS message value extracted from the given
* text node.
*
* @param builder the JS message builder to append parts to
* @param node the node with string literal that contains the message text
* @throws MalformedException if {@code value} contains a reference to
* an
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> unregistered placeholder
*/
private void parseMessageTextNode(Builder builder, Node node)
throws MalformedException {
String value = extractStringFromStringExprNode(node);
while(true) {
int phBegin = value.indexOf(PH_JS_PREFIX);
if (phBegin < 0) {
// Just a string literal
builder.appendStringPart(value);
return;
} else {
if (phBegin > 0) {
// A string literal followed by a placeholder
builder.appendStringPart(value.substring(0, phBegin));
}
// A placeholder. Find where it ends
int phEnd = value.indexOf(PH_JS_SUFFIX, phBegin);
if (phEnd < 0) {
throw new MalformedException(
"Placeholder incorrectly formatted in: " + builder.getKey(),
node);
}
String phName = value.substring(phBegin + PH_JS_PREFIX.length(),
phEnd);
builder.appendPlaceholderReference(phName);
int nextPos = phEnd + PH_JS_SUFFIX.length();
if (nextPos < value.length()) {
// Iterate on the rest of the message value
value = value.substring(nextPos);
} else {
// The message is parsed
return;
}
}
}
}
/** Visit a call to goog.getMsgWithFallback. */
private void visitFallbackFunctionCall(NodeTraversal t, Node call) {
// Check to make sure the function call looks like:
// goog.getMsgWithFallback(MSG_1, MSG_2);
if (call.getChildCount() != 3 ||
!call.getChildAtIndex(1).isName() ||
!call.getChildAtIndex(2).isName()) {
compiler.report(t.makeError(call, BAD_FALLBACK_SYNTAX));
return;
}
Node firstArg = call.getChildAtIndex(1);
JsMessage firstMessage = getTrackedMessage(t, firstArg.getString());
if (firstMessage == null) {
compiler.report(
t.makeError(firstArg, FALLBACK_ARG_ERROR, firstArg.getString()));
return;
}
Node secondArg = firstArg.getNext();
JsMessage secondMessage = getTrackedMessage(
t, call.getChild
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>AtIndex(2).getString());
if (secondMessage == null) {
compiler.report(
t.makeError(secondArg, FALLBACK_ARG_ERROR, secondArg.getString()));
return;
}
processMessageFallback(call, firstMessage, secondMessage);
}
/**
* Processes found JS message. Several examples of "standard" processing
* routines are:
* <ol>
* <li>extract all JS messages
* <li>replace JS messages with localized versions for some specific language
* <li>check that messages have correct syntax and present in localization
* bundle
* </ol>
*
* @param message the found message
* @param definition the definition of the object and usually contains all
* additional message information like message node/parent's node
*/
abstract void processJsMessage(JsMessage message,
JsMessageDefinition definition);
/**
* Processes the goog.getMsgWithFallback primitive.
* goog.getMsgWithFallback(MSG_1, MSG_2);
*
* By default, does nothing.
*/
void processMessageFallback(Node callNode, JsMessage message1,
JsMessage message2) {}
/**
* Returns whether the given JS identifier is a valid JS message name.
*/
boolean isMessageName(String identifier, boolean isNewStyleMessage) {
return identifier.startsWith(MSG_PREFIX) &&
(style == JsMessage.Style.CLOSURE || isNewStyleMessage ||
!identifier.endsWith(DESC_SUFFIX));
}
/**
* Returns whether the given message name is in the unnamed namespace.
*/
private static boolean isUnnamedMessageName(String identifier) {
return MSG_UNNAMED_PATTERN.matcher(identifier).matches();
}
/**
* Returns whether a string is nonempty, begins with a lowercase letter, and
* contains only digits and underscores after the first underscore.
*/
static boolean isLowerCamelCaseWithNumericSuffixes(String input) {
return CAMELCASE_PATTERN.matcher(input).matches();
}
/**
* Returns human-readable name of the given node's type.
*/
private static String getReadableTokenName(Node node) {
return Token.name(node.getType());
}
/**
* Converts the given string from upper-underscore case to
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Type system, or the concrete type system.
*/
private DisambiguateProperties(AbstractCompiler compiler,
TypeSystem<T> typeSystem, Map<String, CheckLevel> propertiesToErrorFor) {
this.compiler = compiler;
this.typeSystem = typeSystem;
this.propertiesToErrorFor = propertiesToErrorFor;
if (!this.propertiesToErrorFor.isEmpty()) {
this.invalidationMap = LinkedHashMultimap.create();
} else {
this.invalidationMap = null;
}
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(
compiler.getLifeCycleStage() == LifeCycleStage.NORMALIZED);
for (TypeMismatch mis : compiler.getTypeValidator().getMismatches()) {
addInvalidatingType(mis.typeA, mis.src);
addInvalidatingType(mis.typeB, mis.src);
}
StaticScope<T> scope = typeSystem.getRootScope();
NodeTraversal.traverse(compiler, externs, new FindExternProperties());
NodeTraversal.traverse(compiler, root, new FindRenameableProperties());
renameProperties();
}
private void recordInvalidationError(JSType t, JSError error) {
if (!t.isObject()) {
return;
}
if (invalidationMap != null) {
invalidationMap.put(t, error);
}
}
/**
* Invalidates the given type, so that no properties on it will be renamed.
*/
private void addInvalidatingType(JSType type, JSError error) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
addInvalidatingType(alt, error);
}
} else if (type.isEnumElementType()) {
addInvalidatingType(
type.toMaybeEnumElementType().getPrimitiveType(), error);
} else {
typeSystem.addInvalidatingType(type);
recordInvalidationError(type, error);
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.getImplicitPrototype() != null) {
typeSystem.addInvalidatingType(objType.getImplicitPrototype());
recordInvalid
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>ationError(objType.getImplicitPrototype(), error);
}
}
}
/** Returns the property for the given name, creating it if necessary. */
protected Property getProperty(String name) {
if (!properties.containsKey(name)) {
properties.put(name, new Property(name));
}
return properties.get(name);
}
/** Public for testing. */
T getTypeWithProperty(String field, T type) {
return typeSystem.getTypeWithProperty(field, type);
}
/** Tracks the current type system scope while traversing. */
private abstract class AbstractScopingCallback implements ScopedCallback {
protected final Stack<StaticScope<T>> scopes =
new Stack<StaticScope<T>>();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
scopes.push(typeSystem.getRootScope());
} else {
scopes.push(typeSystem.getFunctionScope(t.getScopeRoot()));
}
}
@Override
public void exitScope(NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.isGetProp()) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
// If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> must handle them here.
if ((type = typeSystem.getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
handleGetProp(t, n);
} else if (n.isObjectLit()) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (propertiesToErrorFor.containsKey(name)) {
String suggestion = "";
if (type instanceof JSType) {
JSType jsType = (JSType) type;
if (jsType.isAllType() || jsType.isUnknownType()) {
if (n.getFirstChild().isThis()) {
suggestion = "The \"this\" object is unknown in the function,"+
"consider using @this";
} else {
String qName = n.getFirstChild().getQualifiedName();
suggestion = "Consider casting " + qName +
" if you know it's type.";
}
} else {
List<String> errors = Lists.newArrayList();
printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child = child.getNext();
}
}
private void printErrorLocations(List<String> errors, JSType t) {
if (!t.isObject() || t.isAllType()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
printErrorLocations(errors, alt);
}
return;
}
for (JSError error : invalidationMap.get(t)) {
if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) {
return;
}
errors.add(
t.toString() + " at " + error.sourceName + ":" + error.lineNumber);
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOr
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> * @param prop Only types with this property need to be returned. In general
* with type tightening, this will require no special processing, but in
* the case of an unknown JSType, we might need to add in the native
* types since we don't track them, but only if they have the given
* property.
*/
T getType(StaticScope<T> scope, Node node, String prop);
/**
* Returns true if a field reference on this type will invalidate all
* references to that field as candidates for renaming. This is true if the
* type is unknown or all-inclusive, as variables with such a type could be
* references to any object.
*/
boolean isInvalidatingType(T type);
/**
* Informs the given type system that a type is invalidating due to a type
* mismatch found during type checking.
*/
void addInvalidatingType(JSType type);
/**
* Returns a set of types that should be skipped given the given type.
* This is necessary for interfaces when using JSTypes, as all super
* interfaces must also be skipped.
*/
ImmutableSet<T> getTypesToSkipForType(T type);
/**
* Determines whether the given type is one whose properties should not be
* considered for renaming.
*/
boolean isTypeToSkip(T type);
/** Remove null and undefined from the options in the given type. */
T restrictByNotNullOrUndefined(T type);
/**
* Returns the alternatives if this is a type that represents multiple
* types, and null if not. Union and interface types can correspond to
* multiple other types.
*/
Iterable<T> getTypeAlternatives(T type);
/**
* Returns the type in the chain from the given type that contains the given
* field or null if it is not found anywhere.
*/
T getTypeWithProperty(String field, T type);
/**
* Returns the type of the instance of which this is the prototype or null
* if this is not a function prototype.
*/
T getInstanceFromPrototype(T type);
/**
* Records that this property could be referenced from any interface that
* this type, or any type in its superclass chain, implements.
*/
void recordInterfaces(T type, T relatedType,
DisambiguateProperties<T
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>>.Property p);
}
/** Implementation of TypeSystem using JSTypes. */
private static class JSTypeSystem implements TypeSystem<JSType> {
private final Set<JSType> invalidatingTypes;
private JSTypeRegistry registry;
public JSTypeSystem(AbstractCompiler compiler) {
registry = compiler.getTypeRegistry();
invalidatingTypes = Sets.newHashSet(
registry.getNativeType(JSTypeNative.ALL_TYPE),
registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE),
registry.getNativeType(JSTypeNative.NO_TYPE),
registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<JSType> getRootScope() { return null; }
@Override public StaticScope<JSType> getFunctionScope(Node node) {
return null;
}
@Override public JSType getType(
StaticScope<JSType> scope, Node node, String prop) {
if (node.getJSType() == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return node.getJSType();
}
@Override public boolean isInvalidatingType(JSType type) {
if (type == null || invalidatingTypes.contains(type) ||
type.isUnknownType() /* unresolved types */) {
return true;
}
ObjectType objType = ObjectType.cast(type);
return objType != null && !objType.hasReferenceName();
}
@Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
Set<JSType> types = Sets.newHashSet(type);
for (JSType alt : type.toMaybeUnionType().get
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Scope<ConcreteType> scope, Node node, String prop) {
if (scope != null) {
ConcreteType c = tt.inferConcreteType(
(TightenTypes.ConcreteScope) scope, node);
return maybeAddAutoboxes(c, node, prop);
} else {
return null;
}
}
/**
* Add concrete types for autoboxing types if necessary. The concrete type
* system does not track native types, like string, so add them if they are
* present in the JSType for the node.
*/
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, Node node, String prop) {
JSType jsType = node.getJSType();
if (jsType == null) {
return cType;
} else if (jsType.isUnknownType()) {
for (JSTypeNative nativeType : nativeTypes) {
ConcreteType concrete = tt.getConcreteInstance(
tt.getTypeRegistry().getNativeObjectType(nativeType));
if (concrete != null && !concrete.getPropertyType(prop).isNone()) {
cType = cType.unionWith(concrete);
}
}
return cType;
}
return maybeAddAutoboxes(cType, jsType, prop);
}
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, JSType jsType, String prop) {
jsType = jsType.restrictByNotNullOrUndefined();
if (jsType.isUnionType()) {
for (JSType alt : jsType.toMaybeUnionType().getAlternates()) {
cType = maybeAddAutoboxes(cType, alt, prop);
}
return cType;
} else if (jsType.isEnumElementType()) {
return maybeAddAutoboxes(
cType, jsType.toMaybeEnumElementType().getPrimitiveType(), prop);
}
if (jsType.autoboxesTo() != null) {
JSType autoboxed = jsType.autoboxesTo();
return cType.unionWith(tt.getConcreteInstance((ObjectType) autoboxed));
} else if (jsType.unboxesTo() != null) {
return cType.unionWith(tt.getConcreteInstance((ObjectType) jsType));
}
return cType;
}
@Override public boolean isInvalidatingType(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>doc = NodeUtil.getBestJSDocInfo(node);
if (jsdoc == null && !node.isFunction()) {
return "";
}
JSType type = node.getJSType();
if (type == null) {
return "";
} else if (type.isFunctionType()) {
return getFunctionAnnotation(node);
} else if (type.isEnumType()) {
return "/** @enum {" +
type.toMaybeEnumType().getElementsType().toAnnotationString() +
"} */\n";
} else if (!type.isUnknownType()
&& !type.isEmptyType()
&& !type.isVoidType()
&& !type.isFunctionPrototypeType()) {
return "/** @type {" + node.getJSType().toAnnotationString() + "} */\n";
} else {
return "";
}
}
/**
* @param fnNode A node for a function for which to generate a type annotation
*/
private String getFunctionAnnotation(Node fnNode) {
Preconditions.checkState(fnNode.isFunction());
StringBuilder sb = new StringBuilder("/**\n");
JSType type = fnNode.getJSType();
if (type == null || type.isUnknownType()) {
return "";
}
FunctionType funType = type.toMaybeFunctionType();
// We need to use the child nodes of the function as the nodes for the
// parameters of the function type do not have the real parameter names.
// FUNCTION
// NAME
// LP
// NAME param1
// NAME param2
if (fnNode != null) {
Node paramNode = NodeUtil.getFunctionParameters(fnNode).getFirstChild();
// Param types
for (Node n : funType.getParameters()) {
// Bail out if the paramNode is not there.
if (paramNode == null) {
break;
}
sb.append(" * ");
appendAnnotation(sb, "param", getParameterNodeJSDocType(n));
sb.append(" ")
.append(paramNode.getString())
.append("\n");
paramNode = paramNode.getNext();
}
}
// Return type
JSType retType = funType.getReturnType();
if (retType != null && !retType.isUnknownType() && !retType
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> we model scopes but handles some
// additional cases that are not handled by the current Scope object.
// Specifically, a Scope currently has only two concepts of scope (global,
// and function local). But there are in reality a couple of additional
// case to worry about:
// catch expressions
// function expressions names
// Both belong to a scope by themselves.
private Deque<Renamer> nameStack = new ArrayDeque<Renamer>();
private final Renamer rootRenamer;
MakeDeclaredNamesUnique() {
this(new ContextualRenamer());
}
MakeDeclaredNamesUnique(Renamer renamer) {
this.rootRenamer = renamer;
}
static CompilerPass getContextualRenameInverter(AbstractCompiler compiler) {
return new ContextualRenameInverter(compiler);
}
@Override
public void enterScope(NodeTraversal t) {
Node declarationRoot = t.getScopeRoot();
Renamer renamer;
if (nameStack.isEmpty()) {
// If the contextual renamer is being used, the starting context can not
// be a function.
Preconditions.checkState(
!declarationRoot.isFunction() ||
!(rootRenamer instanceof ContextualRenamer));
Preconditions.checkState(t.inGlobalScope());
renamer = rootRenamer;
} else {
renamer = nameStack.peek().forChildScope();
}
if (!declarationRoot.isFunction()) {
// Add the block declarations
findDeclaredNames(declarationRoot, null, renamer);
}
nameStack.push(renamer);
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
nameStack.pop();
}
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.FUNCTION:
{
// Add recursive function name, if needed.
// NOTE: "enterScope" is called after we need to pick up this name.
Renamer renamer = nameStack.peek().forChildScope();
// If needed, add the function recursive name.
String name = n.getFirstChild().getString();
if (name != null && !name.isEmpty() && parent != null
&&
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> !NodeUtil.isFunctionDeclaration(n)) {
renamer.addDeclaredName(name);
}
nameStack.push(renamer);
}
break;
case Token.PARAM_LIST: {
Renamer renamer = nameStack.peek().forChildScope();
// Add the function parameters
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
String name = c.getString();
renamer.addDeclaredName(name);
}
// Add the function body declarations
Node functionBody = n.getNext();
findDeclaredNames(functionBody, null, renamer);
nameStack.push(renamer);
}
break;
case Token.CATCH:
{
Renamer renamer = nameStack.peek().forChildScope();
String name = n.getFirstChild().getString();
renamer.addDeclaredName(name);
nameStack.push(renamer);
}
break;
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
String newName = getReplacementName(n.getString());
if (newName != null) {
Renamer renamer = nameStack.peek();
if (renamer.stripConstIfReplaced()) {
// TODO(johnlenz): Do we need to do anything about the Javadoc?
n.removeProp(Node.IS_CONSTANT_NAME);
}
n.setString(newName);
t.getCompiler().reportCodeChange();
}
break;
case Token.FUNCTION:
// Remove the function body scope
nameStack.pop();
// Remove function recursive name (if any).
nameStack.pop();
break;
case Token.PARAM_LIST:
// Note: The parameters and function body variables live in the
// same scope, we introduce the scope when in the "shouldTraverse"
// visit of LP, but remove it when when we exit the function above.
break;
case Token.CATCH:
// Remove catch except name from the stack of names.
nameStack.pop();
break;
}
}
/**
* Walks the stack of name maps and finds the replacement name for the
* current scope
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.
*/
private String getReplacementName(String oldName) {
for (Renamer names : nameStack) {
String newName = names.getReplacementName(oldName);
if (newName != null) {
return newName;
}
}
return null;
}
/**
* Traverses the current scope and collects declared names. Does not
* decent into functions or add CATCH exceptions.
*/
private void findDeclaredNames(Node n, Node parent, Renamer renamer) {
// Do a shallow traversal, so don't traverse into function declarations,
// except for the name of the function itself.
if (parent == null
|| !parent.isFunction()
|| n == parent.getFirstChild()) {
if (NodeUtil.isVarDeclaration(n)) {
renamer.addDeclaredName(n.getString());
} else if (NodeUtil.isFunctionDeclaration(n)) {
Node nameNode = n.getFirstChild();
renamer.addDeclaredName(nameNode.getString());
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
findDeclaredNames(c, n, renamer);
}
}
}
/**
* Declared names renaming policy interface.
*/
interface Renamer {
/**
* Called when a declared name is found in the local current scope.
*/
void addDeclaredName(String name);
/**
* @return A replacement name, null if oldName is unknown or should not
* be replaced.
*/
String getReplacementName(String oldName);
/**
* @return Whether the constant-ness of a name should be removed.
*/
boolean stripConstIfReplaced();
/**
* @return A Renamer for a scope within the scope of the current Renamer.
*/
Renamer forChildScope();
}
/**
* Inverts the transformation by {@link ContextualRenamer}, when possible.
*/
static class ContextualRenameInverter
implements ScopedCallback, CompilerPass {
private final AbstractCompiler compiler;
// The set of names referenced in the current scope.
private Set<String> referencedNames = ImmutableSet.of();
// Stack reference sets.
private Deque<Set<String>> referenceStack = new ArrayDeque<Set<String>>();
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>containsSeparator(name) && !getOrginalName(name).isEmpty()) {
String newName = findReplacementName(name);
referencedNames.remove(name);
// Adding a reference to the new name to prevent either the parent
// scopes or the current scope renaming another var to this new name.
referencedNames.add(newName);
List<Node> references = nameMap.get(name);
Preconditions.checkState(references != null);
for (Node n : references) {
Preconditions.checkState(n.isName());
n.setString(newName);
}
compiler.reportCodeChange();
nameMap.remove(name);
}
}
/**
* Find a name usable in the local scope.
*/
private String findReplacementName(String name) {
String original = getOrginalName(name);
String newName = original;
int i = 0;
while (!isValidName(newName)) {
newName = original +
ContextualRenamer.UNIQUE_ID_SEPARATOR + String.valueOf(i++);
}
return newName;
}
/**
* @return Whether the name is valid to use in the local scope.
*/
private boolean isValidName(String name) {
if (TokenStream.isJSIdentifier(name) &&
!referencedNames.contains(name) &&
!name.equals(ARGUMENTS)) {
return true;
}
return false;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (NodeUtil.isReferenceName(node)) {
String name = node.getString();
// Add all referenced names to the set so it is possible to check for
// conflicts.
referencedNames.add(name);
// Store only references to candidate names in the node map.
if (containsSeparator(name)) {
addCandidateNameReference(name, node);
}
}
}
private void addCandidateNameReference(String name, Node n) {
List<Node> nodes = nameMap.get(name);
if (null == nodes) {
nodes = Lists.newLinkedList();
nameMap.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(
propertyName, type, inferred, propertyNode);
}
@Override
public boolean removeProperty(String name) {
return referencedObjType == null ? false :
referencedObjType.removeProperty(name);
}
@Override
public JSType findPropertyType(String propertyName) {
return referencedType.findPropertyType(propertyName);
}
@Override
public JSDocInfo getJSDocInfo() {
return referencedType.getJSDocInfo();
}
@Override
public void setJSDocInfo(JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setJSDocInfo(info);
}
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setPropertyJSDocInfo(propertyName, info);
}
}
@Override
public FunctionType getConstructor() {
return referencedObjType == null ? null :
referencedObjType.getConstructor();
}
@Override
public JSType getParameterType() {
return referencedObjType == null ? null :
referencedObjType.getParameterType();
}
@Override
public JSType getIndexType() {
return referencedObjType == null ? null :
referencedObjType.getIndexType();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return referencedType.visit(visitor);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return referencedType.visit(visitor, that);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setReferencedType(referencedType.resolve(t, scope));
return this;
}
@Override
public String toDebugHashCodeString() {
return "{proxy:" + referencedType.toDebugHashCodeString() + "}";
}
@Override
public JSType getTypeOfThis() {
if (referencedObjType != null) {
return referencedObjType.getTypeOfThis();
}
return super.getTypeOfThis();
}
@Override
public JSType collapseUnion() {
if (referencedType.isUnionType()) {
return referencedType.collapseUnion();
}
return this;
}
@Override
public void matchConstraint(JS
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.isFunction() || parent.isVar() ||
parent.isNew()) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.isNew()) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
Preconditions.checkState(objectType.hasReferenceName());
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
// Add the prototype when we're looking at an instance object
if (objectType.isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
if (prototype.hasProperty(propertyName)
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.isAssign() &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>FromNode(Node n) {
Node newParam = newParameterFromNode(n);
if (!newParam.isVarArgs() && !newParam.isOptionalArg()) {
newParam.setOptionalArg(true);
}
return newParam;
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
paramNode.setJSType(type);
root.addChildToBack(paramNode);
return paramNode;
}
public Node build() {
return root;
}
private boolean hasOptionalOrVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null &&
(lastChild.isOptionalArg() || lastChild.isVarArgs());
}
public boolean hasVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null && lastChild.isVarArgs();
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> @param callNode A CALL node.
*/
public String getSingletonGetterClassName(Node callNode);
/**
* In many JS libraries, the function that adds a singleton getter to a class
* adds properties to the class.
*/
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType);
/**
* @return Whether the function is inlinable by convention.
*/
public boolean isInlinableFunction(Node n);
/**
* @return the delegate relationship created by the call or null.
*/
public DelegateRelationship getDelegateRelationship(Node callNode);
/**
* In many JS libraries, the function that creates a delegate relationship
* also adds properties to the delegator and delegate base.
*/
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate);
/**
* @return the name of the delegate superclass.
*/
public String getDelegateSuperclassName();
/**
* Checks for function calls that set the calling conventions on delegate
* methods.
*/
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions);
/**
* Defines the delegate proxy prototype properties. Their types depend on
* properties of the delegate base methods.
*
* @param delegateProxyPrototypes List of delegate proxy prototypes.
*/
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions);
/**
* Gets the name of the global object.
*/
public String getGlobalObject();
/**
* A Bind instance or null.
*/
public Bind describeFunctionBind(Node n);
/**
* A Bind instance or null.
* @param useTypeInfo If we believe type information is reliable enough
* to use to figure out what the bind function is.
*/
public Bind describeFunctionBind(Node n, boolean useTypeInfo);
public static class Bind {
// The target of the bind action
final Node target;
// The node representing the "this" value, maybe null
final Node thisValue;
// The head of a Node list representing the parameters
final Node parameters;
public Bind(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public TypePair getTypesUnderShallowInequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderShallowInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnionType(this);
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseUnionType(this, that);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this); // for circularly defined types.
boolean changed = false;
ImmutableList.Builder<JSType> resolvedTypes = ImmutableList.builder();
for (JSType alternate : alternates) {
JSType newAlternate = alternate.resolve(t, scope);
changed |= (alternate != newAlternate);
resolvedTypes.add(alternate);
}
if (changed) {
Collection<JSType> newAlternates = resolvedTypes.build();
Preconditions.checkState(
newAlternates.hashCode() == this.hashcode);
alternates = newAlternates;
}
return this;
}
@Override
public String toDebugHashCodeString() {
List<String> hashCodes = Lists.newArrayList();
for (JSType a : alternates) {
hashCodes.add(a.toDebugHashCodeString());
}
return "{(" + Joiner.on(",").join(hashCodes) + ")}";
}
@Override
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseNoObjectType();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseNoObjectType(that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "?" : "NoObject";
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> : -1L;
}
if (1 <= c && c <= 9) {
long v = c;
for (int i = 1; i != len; ++i) {
c = str.charAt(i) - '0';
if (!(0 <= c && c <= 9)) {
return -1;
}
v = 10 * v + c;
}
// Check for overflow
if ((v >>> 32) == 0) {
return v;
}
}
}
return -1;
}
static boolean isSpecialProperty(String s) {
return s.equals("__proto__") || s.equals("__parent__");
}
// ------------------
// Statements
// ------------------
public static String getMessage0(String messageId) {
return getMessage(messageId, null);
}
public static String getMessage1(String messageId, Object arg1) {
Object[] arguments = {arg1};
return getMessage(messageId, arguments);
}
/* OPT there's a noticeable delay for the first error! Maybe it'd
* make sense to use a ListResourceBundle instead of a properties
* file to avoid (synchronized) text parsing.
*/
public static String getMessage(String messageId, Object[] arguments) {
final String defaultResource
= "rhino_ast.java.com.google.javascript.rhino.Messages";
Locale locale = Locale.getDefault();
// ResourceBundle does caching.
ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale);
String formatString;
try {
formatString = rb.getString(messageId);
} catch (java.util.MissingResourceException mre) {
throw new RuntimeException
("no message resource found for message property "+ messageId);
}
/*
* It's OK to format the string, even if 'arguments' is null;
* we need to format it anyway, to make double ''s collapse to
* single 's.
*/
// TODO: MessageFormat is not available on pJava
MessageFormat formatter = new MessageFormat(formatString);
return formatter.format(arguments);
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>} if JSDoc information was correctly parsed,
* {@code false} otherwise
*/
boolean parse() {
int lineno;
int charno;
// JSTypes are represented as Rhino AST nodes, and then resolved later.
JSTypeExpression type;
state = State.SEARCHING_ANNOTATION;
skipEOLs();
JsDocToken token = next();
List<ExtendedTypeInfo> extendedTypes = Lists.newArrayList();
// Always record that we have a comment.
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo blockInfo = extractBlockComment(token);
token = blockInfo.token;
if (!blockInfo.string.isEmpty()) {
jsdocBuilder.recordBlockDescription(blockInfo.string);
}
} else {
if (token != JsDocToken.ANNOTATION &&
token != JsDocToken.EOC) {
// Mark that there was a description, but don't bother marking
// what it was.
jsdocBuilder.recordBlockDescription("");
}
}
// Parse the actual JsDoc.
retry: for (;;) {
switch (token) {
case ANNOTATION:
if (state == State.SEARCHING_ANNOTATION) {
state = State.SEARCHING_NEWLINE;
lineno = stream.getLineno();
charno = stream.getCharno();
String annotationName = stream.getString();
Annotation annotation = annotationNames.get(annotationName);
if (annotation == null) {
parser.addParserWarning("msg.bad.jsdoc.tag", annotationName,
stream.getLineno(), stream.getCharno());
} else {
// Mark the beginning of the annotation.
jsdocBuilder.markAnnotation(annotationName, lineno, charno);
switch (annotation) {
case AUTHOR:
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo authorInfo = extractSingleLineBlock();
String author = authorInfo.string;
if (author.length() == 0) {
parser.addParserWarning("msg.jsdoc.authormissing",
stream.getLineno(), stream.getCharno());
} else {
jsdocBuilder.addAuthor(author);
}
token = authorInfo.token;
} else {
token = eatTokensUntilEOL
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addTypeWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addTypeWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addTypeWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case HIDDEN:
if (!jsdocBuilder.recordHiddenness()) {
parser.addParserWarning("msg.jsdoc.hidden",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case LENDS:
skipEOLs();
matchingRc = false;
if (match(JsDocToken.LC)) {
token = next();
matchingRc = true;
}
if (match(JsDocToken.STRING)) {
token = next();
if (!jsdocBuilder.recordLends(stream.getString())) {
parser.addTypeWarning("msg.jsdoc.lends.incompatible",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addTypeWarning("msg.jsdoc.lends.missing",
stream.getLineno(), stream.getCharno());
}
if (matchingRc && !match(JsDocToken.RC)) {
parser.addTypeWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case MEANING:
ExtractionInfo meaningInfo =
extractMultilineTextualBlock(token);
String meaning = meaningInfo.string;
token = meaningInfo.token;
if (!jsdocBuilder.recordMeaning(meaning)) {
parser.addParserWarning("msg.jsdoc.meaning.extra",
stream.getLineno(), stream.getCharno());
}
continue retry;
case NO_ALIAS
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> case PARAM:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
type = null;
if (token == JsDocToken.LC) {
type = createJSTypeExpression(
parseAndRecordParamTypeNode(token));
if (type == null) {
// parsing error reported during recursive descent
// recovering parsing
token = eatTokensUntilEOL();
continue retry;
}
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
}
String name = null;
boolean isBracketedParam = JsDocToken.LB == token;
if (isBracketedParam) {
token = next();
}
if (JsDocToken.STRING != token) {
parser.addTypeWarning("msg.missing.variable.name",
lineno, charno);
} else {
name = stream.getString();
if (isBracketedParam) {
token = next();
// Throw out JsDocToolkit's "default" parameter
// annotation. It makes no sense under our type
// system.
if (JsDocToken.EQUALS == token) {
token = next();
if (JsDocToken.STRING == token) {
token = next();
}
}
if (JsDocToken.RB != token) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
} else if (type != null) {
// Make the type expression optional, if it isn't
// already.
type = JSTypeExpression.makeOptionalArg(type);
}
}
// If the param name has a DOT in it, just throw it out
// quietly. We do not handle the JsDocToolkit method
// for handling properties of params.
if (name.indexOf('.') > -1) {
name = null;
} else if (!jsdocBuilder.recordParameter(name, type)) {
if (jsdocBuilder.hasParameter(name)) {
parser.addTypeWarning("msg.dup.variable.name", name,
lineno, charno);
} else {
parser.addTypeWarning("msg.jsdoc.incompat.type", name,
lineno, charno);
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>SEARCHING_NEWLINE) {
state = State.SEARCHING_ANNOTATION;
}
token = next();
continue retry;
default:
if (token == JsDocToken.STAR && state == State.SEARCHING_ANNOTATION) {
token = next();
continue retry;
} else {
state = State.SEARCHING_NEWLINE;
token = eatTokensUntilEOL();
continue retry;
}
}
// next token
token = next();
}
}
private void checkExtendedTypes(List<ExtendedTypeInfo> extendedTypes) {
for (ExtendedTypeInfo typeInfo : extendedTypes) {
// If interface, record the multiple extended interfaces
if (jsdocBuilder.isInterfaceRecorded()) {
if (!jsdocBuilder.recordExtendedInterface(typeInfo.type)) {
parser.addParserWarning("msg.jsdoc.extends.duplicate",
typeInfo.lineno, typeInfo.charno);
}
} else {
if (!jsdocBuilder.recordBaseType(typeInfo.type)) {
parser.addTypeWarning("msg.jsdoc.incompat.type",
typeInfo.lineno, typeInfo.charno);
}
}
}
}
/**
* Parse a {@code @suppress} tag of the form
* {@code @suppress{warning1|warning2}}.
*
* @param token The current token.
*/
private JsDocToken parseSuppressTag(JsDocToken token) {
if (token == JsDocToken.LC) {
Set<String> suppressions = new HashSet<String>();
while (true) {
if (match(JsDocToken.STRING)) {
String name = stream.getString();
if (!suppressionNames.contains(name)) {
parser.addParserWarning("msg.jsdoc.suppress.unknown", name,
stream.getLineno(), stream.getCharno());
}
suppressions.add(stream.getString());
token = next();
} else {
parser.addParserWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
return token;
}
if (match(JsDocToken.PIPE)) {
token = next();
} else {
break;
}
}
if (!match
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(JsDocToken.RC)) {
parser.addParserWarning("msg.jsdoc.suppress",
stream.getLineno(), stream.getCharno());
} else {
token = next();
if (!jsdocBuilder.recordSuppressions(suppressions)) {
parser.addParserWarning("msg.jsdoc.suppress.duplicate",
stream.getLineno(), stream.getCharno());
}
}
}
return token;
}
/**
* Parse a {@code @modifies} tag of the form
* {@code @modifies{this|arguments|param}}.
*
* @param token The current token.
*/
private JsDocToken parseModifiesTag(JsDocToken token) {
if (token == JsDocToken.LC) {
Set<String> modifies = new HashSet<String>();
while (true) {
if (match(JsDocToken.STRING)) {
String name = stream.getString();
if (!modifiesAnnotationKeywords.contains(name)
&& !jsdocBuilder.hasParameter(name)) {
parser.addParserWarning("msg.jsdoc.modifies.unknown", name,
stream.getLineno(), stream.getCharno());
}
modifies.add(stream.getString());
token = next();
} else {
parser.addParserWarning("msg.jsdoc.modifies",
stream.getLineno(), stream.getCharno());
return token;
}
if (match(JsDocToken.PIPE)) {
token = next();
} else {
break;
}
}
if (!match(JsDocToken.RC)) {
parser.addParserWarning("msg.jsdoc.modifies",
stream.getLineno(), stream.getCharno());
} else {
token = next();
if (!jsdocBuilder.recordModifies(modifies)) {
parser.addParserWarning("msg.jsdoc.modifies.duplicate",
stream.getLineno(), stream.getCharno());
}
}
}
return token;
}
/**
* Looks for a type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> endLineno = stream.getLineno();
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(typeNode, lineno, startCharno,
endLineno, endCharno, true);
}
return typeNode;
}
/**
* Looks for a parameter type expression at the current token and if found,
* returns it. Note that this method consumes input.
*
* @param token The current token.
* @param lineno The line of the type expression.
* @param startCharno The starting character position of the type expression.
* @param matchingLC Whether the type expression starts with a "{".
* @param onlyParseSimpleNames If true, only simple type names are parsed
* (via a call to parseTypeNameAnnotation instead of
* parseTypeExpressionAnnotation).
* @return The type expression found or null if none.
*/
private Node parseAndRecordTypeNode(JsDocToken token, int lineno,
int startCharno,
boolean matchingLC,
boolean onlyParseSimpleNames) {
Node typeNode = null;
if (onlyParseSimpleNames) {
typeNode = parseTypeNameAnnotation(token);
} else {
typeNode = parseTypeExpressionAnnotation(token);
}
if (typeNode != null) {
int endLineno = stream.getLineno();
int endCharno = stream.getCharno();
jsdocBuilder.markTypeNode(
typeNode, lineno, startCharno, endLineno, endCharno, matchingLC);
}
return typeNode;
}
/**
* Converts a JSDoc token to its string representation.
*/
private String toString(JsDocToken token) {
switch (token) {
case ANNOTATION:
return "@" + stream.getString();
case BANG:
return "!";
case COMMA:
return ",";
case COLON:
return ":";
case GT:
return ">";
case LB:
return "[";
case LC:
return "{";
case LP:
return "(";
case LT:
return ".<";
case QMARK:
return "?";
case PIPE:
return "|";
case RB:
return "]";
case RC:
return "}";
case RP:
return ")";
case ST
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>AR:
return "*";
case ELLIPSIS:
return "...";
case EQUALS:
return "=";
case STRING:
return stream.getString();
default:
throw new IllegalStateException(token.toString());
}
}
/**
* Constructs a new {@code JSTypeExpression}.
* @param n A node. May be null.
*/
private JSTypeExpression createJSTypeExpression(Node n) {
return n == null ? null :
new JSTypeExpression(n, getSourceName());
}
/**
* Tuple for returning both the string extracted and the
* new token following a call to any of the extract*Block
* methods.
*/
private static class ExtractionInfo {
private final String string;
private final JsDocToken token;
public ExtractionInfo(String string, JsDocToken token) {
this.string = string;
this.token = token;
}
}
/**
* Tuple for recording extended types
*/
private static class ExtendedTypeInfo {
final JSTypeExpression type;
final int lineno;
final int charno;
public ExtendedTypeInfo(JSTypeExpression type, int lineno, int charno) {
this.type = type;
this.lineno = lineno;
this.charno = charno;
}
}
/**
* Extracts the text found on the current line starting at token. Note that
* token = token.info; should be called after this method is used to update
* the token properly in the parser.
*
* @return The extraction information.
*/
private ExtractionInfo extractSingleLineBlock() {
// Get the current starting point.
stream.update();
int lineno = stream.getLineno();
int charno = stream.getCharno() + 1;
String line = stream.getRemainingJSDocLine().trim();
// Record the textual description.
if (line.length() > 0) {
jsdocBuilder.markText(line, lineno, charno, lineno,
charno + line.length());
}
return new ExtractionInfo(line, next());
}
private ExtractionInfo extractMultilineTextualBlock(JsDocToken token) {
return extractMultilineTextualBlock(token, WhitespaceOption.SINGLE_LINE);
}
private enum Wh
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.LB) {
skipEOLs();
return parseArrayType(next());
} else if (token == JsDocToken.LC) {
skipEOLs();
return parseRecordType(next());
} else if (token == JsDocToken.LP) {
skipEOLs();
return parseUnionType(next());
} else if (token == JsDocToken.STRING) {
String string = stream.getString();
if ("function".equals(string)) {
skipEOLs();
return parseFunctionType(next());
} else if ("null".equals(string) || "undefined".equals(string)) {
return newStringNode(string);
} else {
return parseTypeName(token);
}
}
restoreLookAhead(token);
return reportGenericTypeSyntaxWarning();
}
/**
* TypeName := NameExpression | NameExpression TypeApplication
* TypeApplication := '.<' TypeExpressionList '>'
* TypeExpressionList := TypeExpression // a white lie
*/
private Node parseTypeName(JsDocToken token) {
if (token != JsDocToken.STRING) {
return reportGenericTypeSyntaxWarning();
}
String typeName = stream.getString();
int lineno = stream.getLineno();
int charno = stream.getCharno();
while (match(JsDocToken.EOL) &&
typeName.charAt(typeName.length() - 1) == '.') {
skipEOLs();
if (match(JsDocToken.STRING)) {
next();
typeName += stream.getString();
}
}
Node typeNameNode = newStringNode(typeName, lineno, charno);
if (match(JsDocToken.LT)) {
next();
skipEOLs();
Node memberType = parseTypeExpressionList(next());
if (memberType != null) {
typeNameNode.addChildToFront(memberType);
skipEOLs();
if (!match(JsDocToken.GT)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.gt");
}
next();
}
}
return typeNameNode;
}
/**
* FunctionType := 'function' FunctionSignatureType
* FunctionSignatureType :=
* TypeParameters '(' 'this' ':' TypeName, ParametersType ')' ResultType
*/
private Node parseFunctionType(JsDocToken token) {
// NOTE(nicks
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>antos): We're not implementing generics at the moment, so
// just throw out TypeParameters.
if (token != JsDocToken.LP) {
restoreLookAhead(token);
return reportTypeSyntaxWarning("msg.jsdoc.missing.lp");
}
Node functionType = newNode(Token.FUNCTION);
Node parameters = null;
skipEOLs();
if (!match(JsDocToken.RP)) {
token = next();
boolean hasParams = true;
if (token == JsDocToken.STRING) {
String tokenStr = stream.getString();
boolean isThis = "this".equals(tokenStr);
boolean isNew = "new".equals(tokenStr);
if (isThis || isNew) {
if (match(JsDocToken.COLON)) {
next();
skipEOLs();
Node contextType = wrapNode(
isThis ? Token.THIS : Token.NEW,
parseTypeName(next()));
if (contextType == null) {
return null;
}
functionType.addChildToFront(contextType);
} else {
return reportTypeSyntaxWarning("msg.jsdoc.missing.colon");
}
if (match(JsDocToken.COMMA)) {
next();
skipEOLs();
token = next();
} else {
hasParams = false;
}
}
}
if (hasParams) {
parameters = parseParametersType(token);
if (parameters == null) {
return null;
}
}
}
if (parameters != null) {
functionType.addChildToBack(parameters);
}
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
skipEOLs();
Node resultType = parseResultType(next());
if (resultType == null) {
return null;
} else {
functionType.addChildToBack(resultType);
}
return functionType;
}
/**
* ParametersType := RestParameterType | NonRestParametersType
* | NonRestParametersType ',' RestParameterType
* RestParameterType := '...' Identifier
* NonRestParametersType := ParameterType ',' NonRestParametersType
* | ParameterType
* | OptionalParametersType
* OptionalParametersType := OptionalParameterType
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = wrapNode(Token.EQUALS, paramType);
}
}
if (paramType == null) {
return null;
}
paramsType.addChildToBack(paramType);
if (isVarArgs) {
break;
}
} while (match(JsDocToken.COMMA));
}
if (isVarArgs && match(JsDocToken.COMMA)) {
return reportTypeSyntaxWarning("msg.jsdoc.function.varargs");
}
// The right paren will be checked by parseFunctionType
return paramsType;
}
/**
* ResultType := <empty> | ':' void | ':' TypeExpression
*/
private Node parseResultType(JsDocToken token) {
skipEOLs();
if (!match(JsDocToken.COLON)) {
return newNode(Token.EMPTY);
}
token = next();
skipEOLs();
if (match(JsDocToken.STRING) && "void".equals(stream.getString())) {
next();
return newNode(Token.VOID);
} else {
return parseTypeExpression(next());
}
}
/**
* UnionType := '(' TypeUnionList ')'
* TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList
*
* We've removed the empty union type.
*/
private Node parseUnionType(JsDocToken token) {
return parseUnionTypeWithAlternate(token, null);
}
/**
* Create a new union type, with an alternate that has already been
* parsed. The alternate may be null.
*/
private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) {
Node union = newNode(Token.PIPE);
if (alternate != null) {
union.addChildToBack(alternate);
}
Node expr = null;
do {
if (expr != null) {
skipEOLs();
token = next();
Preconditions.checkState(
token == JsDocToken.PIPE || token == JsDocToken.COMMA);
boolean isPipe = token == JsDocToken.PIPE;
if (isPipe && match(JsDocToken.PIPE)) {
// We support double pipes for backwards compatibility.
next();
}
skipEOLs();
token = next();
}
expr = parseTypeExpression(token);
if (expr == null) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> do {
Node fieldType = parseFieldType(token);
if (fieldType == null) {
return null;
}
fieldTypeList.addChildToBack(fieldType);
skipEOLs();
if (!match(JsDocToken.COMMA)) {
break;
}
// Move to the comma token.
next();
// Move to the token passed the comma.
skipEOLs();
token = next();
} while (true);
return fieldTypeList;
}
/**
* FieldType := FieldName | FieldName ':' TypeExpression
*/
private Node parseFieldType(JsDocToken token) {
Node fieldName = parseFieldName(token);
if (fieldName == null) {
return null;
}
skipEOLs();
if (!match(JsDocToken.COLON)) {
return fieldName;
}
// Move to the colon.
next();
// Move to the token after the colon and parse
// the type expression.
skipEOLs();
Node typeExpression = parseTypeExpression(next());
if (typeExpression == null) {
return null;
}
Node fieldType = newNode(Token.COLON);
fieldType.addChildToBack(fieldName);
fieldType.addChildToBack(typeExpression);
return fieldType;
}
/**
* FieldName := NameExpression | StringLiteral | NumberLiteral |
* ReservedIdentifier
*/
private Node parseFieldName(JsDocToken token) {
switch (token) {
case STRING:
String string = stream.getString();
return newStringNode(string);
default:
return null;
}
}
private Node wrapNode(int type, Node n) {
return n == null ? null :
new Node(type, n, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newNode(int type) {
return new Node(type, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newStringNode(String s) {
return newStringNode(s, stream.getLineno(), stream.getCharno());
}
private Node newStringNode(String s, int lineno, int charno) {
Node n = Node.newString(s, lineno, charno).clonePropsFrom(templateNode);
n.setLength
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
public void process(Node externs, Node root) {
SimpleDefinitionFinder defFinder = new SimpleDefinitionFinder(compiler);
defFinder.process(externs, root);
// Gather the list of function nodes that have @nosideeffects annotations.
// For use by SetNoSideEffectCallProperty.
NodeTraversal.traverse(
compiler, externs, new GatherNoSideEffectFunctions(true));
NodeTraversal.traverse(
compiler, root, new GatherNoSideEffectFunctions(false));
NodeTraversal.traverse(compiler, root,
new SetNoSideEffectCallProperty(defFinder));
}
/**
* Determines if the type of the value of the RHS expression can
* be a function node.
*/
private static boolean definitionTypeContainsFunctionType(Definition def) {
Node rhs = def.getRValue();
if (rhs == null) {
return true;
}
switch (rhs.getType()) {
case Token.ASSIGN:
case Token.AND:
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.FUNCTION:
case Token.HOOK:
case Token.NAME:
case Token.NEW:
case Token.OR:
return true;
default:
return false;
}
}
/**
* Get the value of the @nosideeffects annotation stored in the
* doc info.
*/
private static boolean hasNoSideEffectsAnnotation(Node node) {
JSDocInfo docInfo = node.getJSDocInfo();
return docInfo != null && docInfo.isNoSideEffects();
}
/**
* Gather function nodes that have @nosideeffects annotations.
*/
private class GatherNoSideEffectFunctions extends AbstractPostOrderCallback {
private final boolean inExterns;
GatherNoSideEffectFunctions(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
if (!inExterns && hasNoSideEffectsAnnotation(node)) {
traversal.report(node, INVALID_NO_SIDE_EFFECT_ANNOTATION);
}
if (node.isGetProp()) {
if (parent.isExprResult() &&
hasNoSideEffectsAnnotation(node))
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Compiler compiler) {
this.compiler = compiler;
this.definitionSiteMap = Maps.newLinkedHashMap();
this.nameDefinitionMultimap = LinkedHashMultimap.create();
this.nameUseSiteMultimap = LinkedHashMultimap.create();
}
/**
* Returns the collection of definition sites found during traversal.
*
* @return definition site collection.
*/
public Collection<DefinitionSite> getDefinitionSites() {
return definitionSiteMap.values();
}
private DefinitionSite getDefinitionAt(Node node) {
return definitionSiteMap.get(node);
}
DefinitionSite getDefinitionForFunction(Node function) {
Preconditions.checkState(function.isFunction());
return getDefinitionAt(getNameNodeFromFunctionNode(function));
}
@Override
public Collection<Definition> getDefinitionsReferencedAt(Node useSite) {
if (definitionSiteMap.containsKey(useSite)) {
return null;
}
if (useSite.isGetProp()) {
String propName = useSite.getLastChild().getString();
if (propName.equals("apply") || propName.equals("call")) {
useSite = useSite.getFirstChild();
}
}
String name = getSimplifiedName(useSite);
if (name != null) {
Collection<Definition> defs = nameDefinitionMultimap.get(name);
if (!defs.isEmpty()) {
return defs;
} else {
return null;
}
} else {
return null;
}
}
@Override
public void process(Node externs, Node source) {
NodeTraversal.traverse(
compiler, externs, new DefinitionGatheringCallback(true));
NodeTraversal.traverse(
compiler, source, new DefinitionGatheringCallback(false));
NodeTraversal.traverse(
compiler, source, new UseSiteGatheringCallback());
}
/**
* Returns a collection of use sites that may refer to provided
* definition. Returns an empty collection if the definition is not
* used anywhere.
*
* @param definition Definition of interest.
* @return use site collection.
*/
Collection<UseSite> getUseSites(Definition definition) {
String name = getSimplifiedName(definition.getLValue());
return nameUseSiteMultimap.get(name);
}
/**
* Extract a name from a node
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>. In the case of GETPROP nodes,
* replace the namespace or object expression with "this" for
* simplicity and correctness at the expense of inefficiencies due
* to higher chances of name collisions.
*
* TODO(user) revisit. it would be helpful to at least use fully
* qualified names in the case of namespaces. Might not matter as
* much if this pass runs after "collapsing properties".
*/
private static String getSimplifiedName(Node node) {
if (node.isName()) {
String name = node.getString();
if (name != null && !name.isEmpty()) {
return name;
} else {
return null;
}
} else if (node.isGetProp()) {
return "this." + node.getLastChild().getString();
}
return null;
}
private class DefinitionGatheringCallback extends AbstractPostOrderCallback {
private boolean inExterns;
DefinitionGatheringCallback(boolean inExterns) {
this.inExterns = inExterns;
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
// Arguments of external functions should not count as name
// definitions. They are placeholder names for documentation
// purposes only which are not reachable from anywhere.
if (inExterns && node.isName() && parent.isParamList()) {
return;
}
Definition def =
DefinitionsRemover.getDefinition(node, inExterns);
if (def != null) {
String name = getSimplifiedName(def.getLValue());
if (name != null) {
Node rValue = def.getRValue();
if ((rValue != null) &&
!NodeUtil.isImmutableValue(rValue) &&
!rValue.isFunction()) {
// Unhandled complex expression
Definition unknownDef =
new UnknownDefinition(def.getLValue(), inExterns);
def = unknownDef;
}
// TODO(johnlenz) : remove this stub dropping code if it becomes
// illegal to have untyped stubs in the externs definitions.
if (inExterns) {
// We need special handling of untyped externs stubs here:
// the stub should be dropped if the name is
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>ches the case where an object literal in goog.reflect.object
// and a prototype method have the same property name.
// NOTE(nicksantos): Maps and trogedit both do this by different
// mechanisms.
Node nameNode = site.node;
Collection<Definition> singleSiteDefinitions =
getDefinitionsReferencedAt(nameNode);
if (singleSiteDefinitions.size() > 1) {
return false;
}
Preconditions.checkState(!singleSiteDefinitions.isEmpty());
Preconditions.checkState(singleSiteDefinitions.contains(definition));
}
return true;
}
/**
* @return Whether the definition is directly exported.
*/
private boolean isExported(Definition definition) {
// Assume an exported method result is used.
Node lValue = definition.getLValue();
if (lValue == null) {
return true;
}
String partialName;
if (lValue.isGetProp()) {
partialName = lValue.getLastChild().getString();
} else if (lValue.isName()) {
partialName = lValue.getString();
} else {
// GETELEM is assumed to be an export or other expression are unknown
// uses.
return true;
}
CodingConvention codingConvention = compiler.getCodingConvention();
if (codingConvention.isExported(partialName)) {
return true;
}
return false;
}
/**
* @return Whether the function is defined in a non-aliasing expression.
*/
static boolean isSimpleFunctionDeclaration(Node fn) {
Node parent = fn.getParent();
Node gramps = parent.getParent();
// Simple definition finder doesn't provide useful results in some
// cases, specifically:
// - functions with recursive definitions
// - functions defined in object literals
// - functions defined in array literals
// Here we defined a set of known function declaration that are 'ok'.
// Some projects seem to actually define "JSCompiler_renameProperty"
// rather than simply having an extern definition. Don't mess with it.
Node nameNode = SimpleDefinitionFinder.getNameNodeFromFunctionNode(fn);
if (nameNode != null
&& nameNode.isName()) {
String name = nameNode.getString();
if (name.equals(NodeUtil.JSC_PROPERTY_NAME_FN
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) ||
name.equals(
ObjectPropertyStringPreprocess.EXTERN_OBJECT_PROPERTY_STRING)) {
return false;
}
}
// example: function a(){};
if (NodeUtil.isFunctionDeclaration(fn)) {
return true;
}
// example: a = function(){};
// example: var a = function(){};
if (fn.getFirstChild().getString().isEmpty()
&& (NodeUtil.isExprAssign(gramps) || parent.isName())) {
return true;
}
return false;
}
/**
* @return the node defining the name for this function (if any).
*/
static Node getNameNodeFromFunctionNode(Node function) {
Preconditions.checkState(function.isFunction());
if (NodeUtil.isFunctionDeclaration(function)) {
return function.getFirstChild();
} else {
Node parent = function.getParent();
if (NodeUtil.isVarDeclaration(parent)) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (NodeUtil.isObjectLitKey(parent, parent.getParent())) {
return parent;
}
}
return null;
}
/**
* Traverse a node and its children and remove any references to from
* the structures.
*/
void removeReferences(Node node) {
if (DefinitionsRemover.isDefinitionNode(node)) {
DefinitionSite defSite = definitionSiteMap.get(node);
if (defSite != null) {
Definition def = defSite.definition;
String name = getSimplifiedName(def.getLValue());
if (name != null) {
this.definitionSiteMap.remove(node);
this.nameDefinitionMultimap.remove(name, node);
}
}
} else {
Node useSite = node;
if (useSite.isGetProp()) {
String propName = useSite.getLastChild().getString();
if (propName.equals("apply") || propName.equals("call")) {
useSite = useSite.getFirstChild();
}
}
String name = getSimplifiedName(useSite);
if (name != null) {
this.nameUseSiteMultimap.remove(name, new UseSite(useSite, null, null));
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.javascript.jscomp.ControlFlowGraph.AbstractCfgNodeTraversalCallback;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.LatticeElement;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Computes reaching definition for all use of each variables.
*
* A definition of {@code A} in {@code A = foo()} is a reaching definition of
* the use of {@code A} in {@code alert(A)} if all paths from entry node must
* reaches that definition and it is the last definition before the use.
*
*/
final class MustBeReachingVariableDef extends
DataFlowAnalysis<Node, MustBeReachingVariableDef.MustDef> {
// The scope of the function that we are analyzing.
private final Scope jsScope;
private final AbstractCompiler compiler;
private final Set<Var> escaped;
MustBeReachingVariableDef(
ControlFlowGraph<Node>
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> cfg, Scope jsScope, AbstractCompiler compiler) {
super(cfg, new MustDefJoin());
this.jsScope = jsScope;
this.compiler = compiler;
this.escaped = Sets.newHashSet();
computeEscaped(jsScope, escaped, compiler);
}
/**
* Abstraction of a local variable definition. It represents the node which
* a local variable is defined as well as a set of other local variables that
* this definition reads from. For example N: a = b + foo.bar(c). The
* definition node will be N, the depending set would be {b,c}.
*/
static class Definition {
final Node node;
final Set<Var> depends = Sets.newHashSet();
private boolean unknownDependencies = false;
Definition(Node node) {
this.node = node;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Definition)) {
return false;
}
Definition otherDef = (Definition) other;
// If the var has the same definition node we can assume they have the
// same depends set.
return otherDef.node == node;
}
}
/**
* Must reaching definition lattice representation. It captures a product
* lattice for each local (non-escaped) variable. The sub-lattice is
* a n + 2 element lattice with all the {@link Definition} in the program,
* TOP and BOTTOM.
*
* <p>Since this is a Must-Define analysis, BOTTOM represents the case where
* there might be more than one reaching definition for the variable.
*
*
* (TOP)
* / | | \
* N1 N2 N3 ....Nn
* \ | | /
* (BOTTOM)
*
*/
static final class MustDef implements LatticeElement {
// TODO(user): Use bit vector instead of maps might get better
// performance. Change it after this is tested to be fully functional.
// When a Var "A" = "TOP", "A" does not exist in reachingDef's keySet.
// When a Var "A" = Node N, "A" maps to that node.
// When a Var "A" = "BOTTOM", "A" maps to null
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.
final Map<Var, Definition> reachingDef;
public MustDef() {
reachingDef = Maps.newHashMap();
}
public MustDef(Iterator<Var> vars) {
this();
while(vars.hasNext()) {
Var var = vars.next();
// Every variable in the scope is defined once in the beginning of the
// function: all the declared variables are undefined, all functions
// have been assigned and all arguments has its value from the caller.
reachingDef.put(var, new Definition(var.scope.getRootNode()));
}
}
/**
* Copy constructor.
*
* @param other The constructed object is a replicated copy of this element.
*/
public MustDef(MustDef other) {
reachingDef = Maps.newHashMap(other.reachingDef);
}
@Override
public boolean equals(Object other) {
return (other instanceof MustDef) &&
((MustDef) other).reachingDef.equals(this.reachingDef);
}
}
private static class MustDefJoin extends JoinOp.BinaryJoinOp<MustDef> {
@Override
public MustDef apply(MustDef a, MustDef b) {
MustDef result = new MustDef();
Map<Var, Definition> resultMap = result.reachingDef;
// Take the join of all variables that are not TOP in this.
for (Map.Entry<Var, Definition> varEntry : a.reachingDef.entrySet()) {
Var var = varEntry.getKey();
Definition aDef = varEntry.getValue();
if (aDef == null) {
// "a" is BOTTOM implies that the variable has more than one possible
// definition. We set the join of this to be BOTTOM regardless of what
// "b" might be.
resultMap.put(var, null);
continue;
}
Node aNode = aDef.node;
if (b.reachingDef.containsKey(var)) {
Definition bDef = b.reachingDef.get(var);
if (aDef.equals(bDef)) {
resultMap.put(var, aDef);
} else {
resultMap.put(var, null);
}
} else {
resultMap.put(var, aDef);
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> // Take the join of all variables that are not TOP in other but it is TOP
// in this.
for (Map.Entry<Var, Definition> entry : b.reachingDef.entrySet()) {
Var var = entry.getKey();
if (!a.reachingDef.containsKey(var)) {
resultMap.put(var, entry.getValue());
}
}
return result;
}
}
@Override
boolean isForward() {
return true;
}
@Override
MustDef createEntryLattice() {
return new MustDef(jsScope.getVars());
}
@Override
MustDef createInitialEstimateLattice() {
return new MustDef();
}
@Override
MustDef flowThrough(Node n, MustDef input) {
// TODO(user): We are doing a straight copy from input to output. There
// might be some opportunities to cut down overhead.
MustDef output = new MustDef(input);
// TODO(user): This must know about ON_EX edges but it should handle
// it better than what we did in liveness. Because we are in a forward mode,
// we can used the branched forward analysis.
computeMustDef(n, n, output, false);
return output;
}
/**
* @param n The node in question.
* @param cfgNode The node to add
* @param conditional true if the definition is not always executed.
*/
private void computeMustDef(
Node n, Node cfgNode, MustDef output, boolean conditional) {
switch (n.getType()) {
case Token.BLOCK:
case Token.FUNCTION:
return;
case Token.WHILE:
case Token.DO:
case Token.IF:
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
return;
case Token.FOR:
if (!NodeUtil.isForIn(n)) {
computeMustDef(
NodeUtil.getConditionExpression(n), cfgNode, output, conditional);
} else {
// for(x in y) {...}
Node lhs = n.getFirstChild();
Node rhs = lhs.getNext();
if (lhs.isVar()) {
lhs = lhs.getLastChild(); // for(var x in y) {...}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
if (lhs.isName()) {
addToDefIfLocal(lhs.getString(), cfgNode, rhs, output);
}
}
return;
case Token.AND:
case Token.OR:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.HOOK:
computeMustDef(n.getFirstChild(), cfgNode, output, conditional);
computeMustDef(n.getFirstChild().getNext(), cfgNode, output, true);
computeMustDef(n.getLastChild(), cfgNode, output, true);
return;
case Token.VAR:
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (c.hasChildren()) {
computeMustDef(c.getFirstChild(), cfgNode, output, conditional);
addToDefIfLocal(c.getString(), conditional ? null : cfgNode,
c.getFirstChild(), output);
}
}
return;
default:
if (NodeUtil.isAssignmentOp(n)) {
if (n.getFirstChild().isName()) {
Node name = n.getFirstChild();
computeMustDef(name.getNext(), cfgNode, output, conditional);
addToDefIfLocal(name.getString(), conditional ? null : cfgNode,
n.getLastChild(), output);
return;
} else if (NodeUtil.isGet(n.getFirstChild())) {
// Treat all assignments to arguments as redefining the
// parameters itself.
Node obj = n.getFirstChild().getFirstChild();
if (obj.isName() && "arguments".equals(obj.getString())) {
// TODO(user): More accuracy can be introduced
// i.e. We know exactly what arguments[x] is if x is a constant
// number.
escapeParameters(output);
}
}
}
if (n.isName() && "arguments".equals(n.getString())) {
escapeParameters(output);
}
// DEC and INC actually defines the variable.
if (n.isDec() || n.isInc()) {
Node target = n.getFirstChild();
if (target.isName()) {
addToDefIfLocal(target.getString(),
conditional ? null :
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> cfgNode, null, output);
return;
}
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
computeMustDef(c, cfgNode, output, conditional);
}
}
}
/**
* Set the variable lattice for the given name to the node value in the def
* lattice. Do nothing if the variable name is one of the escaped variable.
*
* @param node The CFG node where the definition should be record to.
* {@code null} if this is a conditional define.
*/
private void addToDefIfLocal( String name, @Nullable Node node,
@Nullable Node rValue, MustDef def) {
Var var = jsScope.getVar(name);
// var might be null because the variable might be defined in the extern
// that we might not traverse.
if (var == null || var.scope != jsScope) {
return;
}
for (Var other : def.reachingDef.keySet()) {
Definition otherDef = def.reachingDef.get(other);
if (otherDef == null) {
continue;
}
if (otherDef.depends.contains(var)) {
def.reachingDef.put(other, null);
}
}
if (!escaped.contains(var)) {
if (node == null) {
def.reachingDef.put(var, null);
} else {
Definition definition = new Definition(node);
if (rValue != null) {
computeDependence(definition, rValue);
}
def.reachingDef.put(var, definition);
}
}
}
private void escapeParameters(MustDef output) {
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var v = i.next();
if (isParameter(v)) {
// Assume we no longer know where the parameter comes from
// anymore.
output.reachingDef.put(v, null);
}
}
// Also, assume we no longer know anything that depends on a parameter.
for (Entry<Var, Definition> pair: output.reachingDef.entrySet()) {
Definition value = pair.getValue();
if (value == null) {
continue;
}
for (Var
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> dep : value.depends) {
if (isParameter(dep)) {
output.reachingDef.put(pair.getKey(), null);
}
}
}
}
private boolean isParameter(Var v) {
return v.getParentNode().isParamList();
}
/**
* Computes all the local variables that rValue reads from and store that
* in the def's depends set.
*/
private void computeDependence(final Definition def, Node rValue) {
NodeTraversal.traverse(compiler, rValue,
new AbstractCfgNodeTraversalCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var dep = jsScope.getVar(n.getString());
if (dep == null) {
def.unknownDependencies = true;
} else {
def.depends.add(dep);
}
}
}
});
}
/**
* Gets the must reaching definition of a given node.
*
* @param name name of the variable. It can only be names of local variable
* that are not function parameters, escaped variables or variables
* declared in catch.
* @param useNode the location of the use where the definition reaches.
*/
Definition getDef(String name, Node useNode) {
Preconditions.checkArgument(getCfg().hasNode(useNode));
GraphNode<Node, Branch> n = getCfg().getNode(useNode);
FlowState<MustDef> state = n.getAnnotation();
return state.getIn().reachingDef.get(jsScope.getVar(name));
}
Node getDefNode(String name, Node useNode) {
Definition def = getDef(name, useNode);
return def == null ? null : def.node;
}
boolean dependsOnOuterScopeVars(Definition def) {
if (def.unknownDependencies) {
return true;
}
for (Var s : def.depends) {
if (s.scope != jsScope) {
return true;
}
}
return false;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
protected JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null) {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(node, qualifiedName, origType, type);
break;
case Token.THIS:
// "this" references aren't currently modeled in the CFG.
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
@Override
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().isEquivalentTo(type)) {
return enumElementType;
} else {
return type;
}
}
@Override
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
@Override
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
@Override
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
@Override
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
@Override
public JSType caseFunctionType(FunctionType type) {
return type;
}
@Override
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
@Override
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
@Override
public JSType caseObjectType(ObjectType type)
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> based on types and casting
* unnecessary.
*
*/
abstract class TypeSafeDispatcher<T> {
abstract T processArrayLiteral(ArrayLiteral literalNode);
abstract T processAssignment(Assignment assignmentNode);
abstract T processAstRoot(AstRoot rootNode);
abstract T processBlock(Block blockNode);
abstract T processBreakStatement(BreakStatement statementNode);
abstract T processCatchClause(CatchClause clauseNode);
abstract T processConditionalExpression(ConditionalExpression exprNode);
abstract T processContinueStatement(ContinueStatement statementNode);
abstract T processDoLoop(DoLoop loopNode);
abstract T processElementGet(ElementGet getNode);
abstract T processEmptyExpression(EmptyExpression exprNode);
abstract T processEmptyStatement(EmptyStatement exprNode);
abstract T processExpressionStatement(ExpressionStatement statementNode);
abstract T processForInLoop(ForInLoop loopNode);
abstract T processForLoop(ForLoop loopNode);
abstract T processFunctionCall(FunctionCall callNode);
abstract T processFunctionNode(FunctionNode functionNode);
abstract T processIfStatement(IfStatement statementNode);
abstract T processInfixExpression(InfixExpression exprNode);
abstract T processKeywordLiteral(KeywordLiteral literalNode);
abstract T processLabel(Label labelNode);
abstract T processLabeledStatement(LabeledStatement statementNode);
abstract T processName(Name nameNode);
abstract T processNewExpression(NewExpression exprNode);
abstract T processNumberLiteral(NumberLiteral literalNode);
abstract T processObjectLiteral(ObjectLiteral literalNode);
abstract T processObjectProperty(ObjectProperty propertyNode);
abstract T processParenthesizedExpression(ParenthesizedExpression exprNode);
abstract T processPropertyGet(PropertyGet getNode);
abstract T processRegExpLiteral(RegExpLiteral literalNode);
abstract T processReturnStatement(ReturnStatement statementNode);
abstract T processScope(Scope scopeNode);
abstract T processStringLiteral(StringLiteral literalNode);
abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T process
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>WithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return (node instanceof EmptyExpression) ?
processEmptyExpression((EmptyExpression) node) :
processEmptyStatement((EmptyStatement) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name)
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> the <code>Boolean()</code> JavaScript cast function
* except it return UNKNOWN for known values with side-effects, use
* getImpureBooleanValue if you don't care about side-effects.
*/
static TernaryValue getPureBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NOT:
return getPureBooleanValue(n.getLastChild()).not();
case Token.NULL:
case Token.FALSE:
return TernaryValue.FALSE;
case Token.VOID:
if (!mayHaveSideEffects(n.getFirstChild())) {
return TernaryValue.FALSE;
}
break;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.REGEXP:
return TernaryValue.TRUE;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
break;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): regex literals as well.
switch (n.getType()) {
case Token.STRING:
case Token.STRING_KEY:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().isName()
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>TRUE;
default:
return (Character.getType(c) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Preconditions.checkState(n.isFunction());
Node parent = n.getParent();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getQualifiedName();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
String name = n.getFirstChild().getQualifiedName();
return name;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
if (!n.isFunction()) {
return null;
}
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.CAST:
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Unary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperator(n)) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (assignTarget.isName()) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - constructor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (!callNode.isNew()) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.isName() &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (!callNode.isCall()) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.isName()) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.isGetProp()) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Math.floor has no side-effects.
// TODO(nicksantos): This is a terrible terrible hack, until
// I create a definitionProvider that understands namespacing.
if (nameNode.getFirstChild().isName()) {
if ("Math.floor".equals(nameNode.getQualifiedName())) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().isRegExp()
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> } else if (nameNode.getFirstChild().isString()
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.isString() || param.isRegExp())) {
return false;
}
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.isCall());
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.isNew());
return n.isOnlyModifiesThisCall();
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.LSH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.CALL:
case Token.GETELEM:
case Token.GETPROP:
// Data values
case Token.ARRAYLIT:
case Token.EMPTY: // TODO(johnlenz): remove this.
case Token.FALSE:
case Token.FUNCTION:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.STRING:
case Token.STRING_KEY:
case Token.THIS:
case Token.TRUE:
return 15;
case Token.CAST:
return 16;
default: throw new Error("Unknown precedence for " +
Token.name(type
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) +
" (type " + type + ")");
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static boolean isNullOrUndefined(Node n) {
return n.isNull() || isUndefined(n);
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean allResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return allResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return allResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return allResultsMatch(n.getFirstChild(), p)
&& allResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return allResultsMatch(n.getFirstChild().getNext(), p)
&& allResultsMatch(n.getLastChild(), p);
default:
return p.apply(n);
}
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean anyResultsMatch(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.CAST:
return anyResultsMatch(n.getFirstChild(), p);
case Token.ASSIGN:
case Token.COMMA:
return anyResultsMatch(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return anyResultsMatch(n.getFirstChild(), p)
|| anyResultsMatch(n.getLastChild(), p);
case Token.HOOK:
return anyResultsMatch(n.getFirstChild().getNext(), p)
|| anyResultsMatch(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> /**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return allResultsMatch(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token.NAME:
String name = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
static class BooleanResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isBooleanResultHelper(n);
}
}
static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE =
new BooleanResultPredicate();
/**
* @return Whether the result of node evaluation is always a boolean
*/
static boolean isBooleanResult(Node n) {
return allResultsMatch(n, BOOLEAN_RESULT_PREDICATE);
}
static boolean isBooleanResultHelper(Node n) {
switch (n.getType()) {
// Primitives
case Token.TRUE:
case Token.FALSE:
// Comparisons
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
// Queries
case Token.IN:
case Token.INSTANCEOF:
// Inversion
case Token.NOT:
// delete operator returns a boolean.
case Token.DELPROP:
return true;
default:
return
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.isCase() || n.isDefaultCase();
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return n.isName() && !n.getString().isEmpty();
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.isTry() && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.isTry()
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise, only its children can be removed.
node.detachChildren();
}
} else if (node.isCatch()) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren();
} else if (node.isBlock()) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (isStatementBlock(parent)
|| isSwitchCase(node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.isVar()) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.isFunction() && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().isScript()
|| n.getParent().getParent().isFunction());
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.isFunction() && !isStatement(n);
}
/**
* Returns whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
static boolean isBleedingFunctionName(Node n) {
return n.isName() && !n.getString().isEmpty() &&
isFunctionExpression(n.getParent());
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock(node.getLastChild
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
// TODO(johnlenz): rename this function
Preconditions.checkArgument(function.isFunction());
return isNameReferenced(
function.getLastChild(),
"arguments",
MATCH_NOT_FUNCTION);
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.isCall()) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.isString()) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.isAssign() && parent.getFirstChild() == n) ||
parent.isVar();
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
* names are
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes sense to treat this as syntactically similar to
* "var x = 0;".
*
* @param n The node
* @return True if n is an L-value.
*/
public static boolean isLValue(Node n) {
Preconditions.checkArgument(n.isName() || n.isGetProp() ||
n.isGetElem());
Node parent = n.getParent();
if (parent == null) {
return false;
}
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == n)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == n)
|| parent.isVar()
|| (parent.isFunction() && parent.getFirstChild() == n)
|| parent.isDec()
|| parent.isInc()
|| parent.isParamList()
|| parent.isCatch();
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = IR.var(
IR.name(nameNode.getString())
.srcref(nameNode))
.srcref(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript());
return addingRoot;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>HashMap();
@Override
public void visit(Node n) {
if (n.isName()) {
Node parent = n.getParent();
if (parent != null && parent.isVar()) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
MATCH_NOT_FUNCTION);
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
/**
* @return Whether the node represents a qualified prototype property.
*/
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (cur.isGetProp()) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> srcReferenceNode) {
Node node = IR.voidNode(IR.number(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = IR.name(name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.srcref(value);
}
Node var = IR.var(nodeName).srcref(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
@Override
public boolean apply(Node n) {
return n.isName() && n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
void visit(Node node);
}
/**
* A pre-order traversal, calling Visitor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor visitor,
Predicate<Node> traverseChildrenPred) {
visitor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, visitor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Visitor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor visitor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, visitor, traverseChildrenPred);
}
}
visitor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.isTry());
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean hasCatchHandler(Node n) {
Preconditions.checkArgument(n.isBlock());
return n.hasChildren() && n.getFirstChild().isCatch();
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
public static Node getFunctionParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name including $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.isGetProp() &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its an
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> // same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> }
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(function.isFunction());
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
/**
* Returns whether this is a target of a call or new.
*/
static boolean isCallOrNewTarget(Node target) {
Node parent = target.getParent();
return parent != null
&& NodeUtil.isCallOrNew(parent)
&& parent.getFirstChild() == target;
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
return parent.getJSDocInfo();
} else if (isObjectLitKey(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> (isObjectLitKey(lValue, lValue.getParent())) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.CAST:
return isExpressionResultUsed(parent);
case Token.HOOK:
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>("NaN");
} else if (value == Double.POSITIVE_INFINITY) {
result = IR.name("Infinity");
} else if (value == Double.NEGATIVE_INFINITY) {
result = IR.neg(IR.name("Infinity"));
} else {
result = IR.number(value);
}
if (srcref != null) {
result.srcrefTree(srcref);
}
return result;
}
static boolean isNaN(Node n) {
if ((n.isName() && n.getString().equals("NaN")) ||
(n.getType() == Token.DIV &&
n.getFirstChild().isNumber() && n.getFirstChild().getDouble() == 0 &&
n.getLastChild().isNumber() && n.getLastChild().getDouble() == 0)) {
return true;
}
return false;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
break;
case Token.LABEL_NAME:
Preconditions.checkState(!n.getString().isEmpty());
addIdentifier(n.getString());
break;
case Token.NAME:
if (first == null || first.isEmpty()) {
addIdentifier(n.getString());
} else {
Preconditions.checkState(childCount == 1);
addIdentifier(n.getString());
cc.addOp("=", true);
if (first.isComma()) {
addExpr(first, NodeUtil.precedence(Token.ASSIGN), Context.OTHER);
} else {
// Add expression, consider nearby code at lowest level of
// precedence.
addExpr(first, 0, getContextForNoInOperator(context));
}
}
break;
case Token.ARRAYLIT:
add("[");
addArrayList(first);
add("]");
break;
case Token.PARAM_LIST:
add("(");
addList(first);
add(")");
break;
case Token.COMMA:
Preconditions.checkState(childCount == 2);
unrollBinaryOperator(n, Token.COMMA, ",", context, Context.OTHER, 0, 0);
break;
case Token.NUMBER:
Preconditions.checkState(childCount == 0);
cc.addNumber(n.getDouble());
break;
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS: {
// All of these unary operators are right-associative
Preconditions.checkState(childCount == 1);
cc.addOp(NodeUtil.opToStrNoFail(type), false);
addExpr(first, NodeUtil.precedence(type), Context.OTHER);
break;
}
case Token.NEG: {
Preconditions.checkState(childCount == 1);
// It's important to our sanity checker that the code
// we print produces the same AST as the code we parse back.
// NEG is a weird case because Rhino parses "- -2" as "2".
if (n.getFirstChild().isNumber()) {
cc.addNumber(-n.getFirstChild().getDouble());
} else {
cc.addOp(NodeUtil.opToStrNoFail(type), false);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> addExpr(first, NodeUtil.precedence(type), Context.OTHER);
}
break;
}
case Token.HOOK: {
Preconditions.checkState(childCount == 3);
int p = NodeUtil.precedence(type);
addExpr(first, p + 1, context);
cc.addOp("?", true);
addExpr(first.getNext(), 1, Context.OTHER);
cc.addOp(":", true);
addExpr(last, 1, Context.OTHER);
break;
}
case Token.REGEXP:
if (!first.isString() ||
!last.isString()) {
throw new Error("Expected children to be strings");
}
String regexp = regexpEscape(first.getString(), outputCharsetEncoder);
// I only use one .add because whitespace matters
if (childCount == 2) {
add(regexp + last.getString());
} else {
Preconditions.checkState(childCount == 1);
add(regexp);
}
break;
case Token.FUNCTION:
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
Preconditions.checkState(childCount == 3);
boolean funcNeedsParens = (context == Context.START_OF_EXPR);
if (funcNeedsParens) {
add("(");
}
add("function");
add(first);
add(first.getNext());
add(last, Context.PRESERVE_BLOCK);
cc.endFunction(context == Context.STATEMENT);
if (funcNeedsParens) {
add(")");
}
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
Preconditions.checkState(n.getParent().isObjectLit());
Preconditions.checkState(childCount == 1);
Preconditions.checkState(first.isFunction());
// Get methods are unnamed
Preconditions.checkState(first.getFirstChild().getString().isEmpty());
if (type == Token.GETTER_DEF) {
// Get methods have no parameters.
Preconditions.checkState(!first.getChildAtIndex(1).hasChildren());
add("get ");
} else {
// Set methods have one parameter.
Preconditions.checkState(first.getChildAtIndex(1).hasOneChild
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>());
add("set ");
}
// The name is on the GET or SET node.
String name = n.getString();
Node fn = first;
Node parameters = fn.getChildAtIndex(1);
Node body = fn.getLastChild();
// Add the property name.
if (!n.isQuotedString() &&
TokenStream.isJSIdentifier(name) &&
// do not encode literally any non-literal characters that were
// Unicode escaped.
NodeUtil.isLatin(name)) {
add(name);
} else {
// Determine if the string is a simple number.
double d = getSimpleNumber(name);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addJsString(n);
}
}
add(parameters);
add(body, Context.PRESERVE_BLOCK);
break;
case Token.SCRIPT:
case Token.BLOCK: {
if (n.getClass() != Node.class) {
throw new Error("Unexpected Node subclass.");
}
boolean preserveBlock = context == Context.PRESERVE_BLOCK;
if (preserveBlock) {
cc.beginBlock();
}
boolean preferLineBreaks =
type == Token.SCRIPT ||
(type == Token.BLOCK &&
!preserveBlock &&
n.getParent() != null &&
n.getParent().isScript());
for (Node c = first; c != null; c = c.getNext()) {
add(c, Context.STATEMENT);
// VAR doesn't include ';' since it gets used in expressions
if (c.isVar()) {
cc.endStatement();
}
if (c.isFunction()) {
cc.maybeLineBreak();
}
// Prefer to break lines in between top-level statements
// because top-level statements are more homogeneous.
if (preferLineBreaks) {
cc.notePreferredLineBreak();
}
}
if (preserveBlock) {
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
}
break;
}
case Token.FOR:
if (childCount == 4) {
add("for(");
if (first.isVar()) {
add(first, Context.IN_FOR_INIT_CLAUSE
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>);
} else {
addExpr(first, 0, Context.IN_FOR_INIT_CLAUSE);
}
add(";");
add(first.getNext());
add(";");
add(first.getNext().getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
} else {
Preconditions.checkState(childCount == 3);
add("for(");
add(first);
add("in");
add(first.getNext());
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
}
break;
case Token.DO:
Preconditions.checkState(childCount == 2);
add("do");
addNonEmptyStatement(first, Context.OTHER, false);
add("while(");
add(last);
add(")");
cc.endStatement();
break;
case Token.WHILE:
Preconditions.checkState(childCount == 2);
add("while(");
add(first);
add(")");
addNonEmptyStatement(
last, getContextForNonEmptyExpression(context), false);
break;
case Token.EMPTY:
Preconditions.checkState(childCount == 0);
break;
case Token.GETPROP: {
Preconditions.checkState(
childCount == 2,
"Bad GETPROP: expected 2 children, but got %s", childCount);
Preconditions.checkState(
last.isString(),
"Bad GETPROP: RHS should be STRING");
boolean needsParens = (first.isNumber());
if (needsParens) {
add("(");
}
addExpr(first, NodeUtil.precedence(type), context);
if (needsParens) {
add(")");
}
add(".");
addIdentifier(last.getString());
break;
}
case Token.GETELEM:
Preconditions.checkState(
childCount == 2,
"Bad GETELEM: expected 2 children but got %s", childCount);
addExpr(first, NodeUtil.precedence(type), context);
add("[");
add(first.getNext());
add("]");
break;
case Token.WITH:
Preconditions.checkState(childCount == 2);
add
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> functions).
if (NodeUtil.containsType(
first, Token.CALL, NodeUtil.MATCH_NOT_FUNCTION)) {
precedence = NodeUtil.precedence(first.getType()) + 1;
}
addExpr(first, precedence, Context.OTHER);
// '()' is optional when no arguments are present
Node next = first.getNext();
if (next != null) {
add("(");
addList(next);
add(")");
}
break;
case Token.STRING_KEY:
Preconditions.checkState(
childCount == 1, "Object lit key must have 1 child");
addJsString(n);
break;
case Token.STRING:
Preconditions.checkState(
childCount == 0, "A string may not have children");
addJsString(n);
break;
case Token.DELPROP:
Preconditions.checkState(childCount == 1);
add("delete ");
add(first);
break;
case Token.OBJECTLIT: {
boolean needsParens = (context == Context.START_OF_EXPR);
if (needsParens) {
add("(");
}
add("{");
for (Node c = first; c != null; c = c.getNext()) {
if (c != first) {
cc.listSeparator();
}
if (c.isGetterDef() || c.isSetterDef()) {
add(c);
} else {
Preconditions.checkState(c.isStringKey());
String key = c.getString();
// Object literal property names don't have to be quoted if they
// are not JavaScript keywords
if (!c.isQuotedString() &&
!TokenStream.isKeyword(key) &&
TokenStream.isJSIdentifier(key) &&
// do not encode literally any non-literal characters that
// were Unicode escaped.
NodeUtil.isLatin(key)) {
add(key);
} else {
// Determine if the string is a simple number.
double d = getSimpleNumber(key);
if (!Double.isNaN(d)) {
cc.addNumber(d);
} else {
addExpr(c, 1, Context.OTHER);
}
}
add(":");
addExpr(c.getFirstChild(), 1, Context
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = s.length();
for (int index = 0; index < len; index++) {
char c = s.charAt(index);
if (c < '0' || c > '9') {
return false;
}
}
return len > 0 && s.charAt(0) != '0';
}
static double getSimpleNumber(String s) {
if (isSimpleNumber(s)) {
try {
long l = Long.parseLong(s);
if (l < NodeUtil.MAX_POSITIVE_INTEGER_NUMBER) {
return l;
}
} catch (NumberFormatException e) {
// The number was too long to parse. Fall through to NaN.
}
}
return Double.NaN;
}
/**
* @return Whether the name is an indirect eval.
*/
private boolean isIndirectEval(Node n) {
return n.isName() && "eval".equals(n.getString()) &&
!n.getBooleanProp(Node.DIRECT_EVAL);
}
/**
* Adds a block or expression, substituting a VOID with an empty statement.
* This is used for "for (...);" and "if (...);" type statements.
*
* @param n The node to print.
* @param context The context to determine how the node should be printed.
*/
private void addNonEmptyStatement(
Node n, Context context, boolean allowNonBlockChild) {
Node nodeToProcess = n;
if (!allowNonBlockChild && !n.isBlock()) {
throw new Error("Missing BLOCK child.");
}
// Strip unneeded blocks, that is blocks with <2 children unless
// the CodePrinter specifically wants to keep them.
if (n.isBlock()) {
int count = getNonEmptyChildCount(n, 2);
if (count == 0) {
if (cc.shouldPreserveExtraBlocks()) {
cc.beginBlock();
cc.endBlock(cc.breakAfterBlockFor(n, context == Context.STATEMENT));
} else {
cc.endStatement(true);
}
return;
}
if (count == 1) {
// Hack around a couple of browser bugs:
// Safari needs a block around function declarations.
// IE6/7 needs a block around DO
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(");
add(n, Context.OTHER);
add(")");
} else {
add(n, context);
}
}
void addList(Node firstInList) {
addList(firstInList, true, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument) {
addList(firstInList, isArrayOrFunctionArgument, Context.OTHER);
}
void addList(Node firstInList, boolean isArrayOrFunctionArgument,
Context lhsContext) {
for (Node n = firstInList; n != null; n = n.getNext()) {
boolean isFirst = n == firstInList;
if (isFirst) {
addExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext);
} else {
cc.listSeparator();
addExpr(n, isArrayOrFunctionArgument ? 1 : 0, Context.OTHER);
}
}
}
/**
* This function adds a comma-separated list as is specified by an ARRAYLIT
* node with the associated skipIndexes array. This is a space optimization
* since we avoid creating a whole Node object for each empty array literal
* slot.
* @param firstInList The first in the node list (chained through the next
* property).
*/
void addArrayList(Node firstInList) {
boolean lastWasEmpty = false;
for (Node n = firstInList; n != null; n = n.getNext()) {
if (n != firstInList) {
cc.listSeparator();
}
addExpr(n, 1, Context.OTHER);
lastWasEmpty = n.isEmpty();
}
if (lastWasEmpty) {
cc.listSeparator();
}
}
void addCaseBody(Node caseBody) {
cc.beginCaseBody();
add(caseBody);
cc.endCaseBody();
}
void addAllSiblings(Node n) {
for (Node c = n; c != null; c = c.getNext()) {
add(c);
}
}
/** Outputs a JS string, using the optimal (single/double) quote character */
private void addJsString(Node n) {
String s = n.getString();
boolean useSlashV = n.getBooleanProp
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(Node.SLASH_V);
if (useSlashV) {
add(jsString(n.getString(), useSlashV));
} else {
String cached = ESCAPED_JS_STRINGS.get(s);
if (cached == null) {
cached = jsString(n.getString(), useSlashV);
ESCAPED_JS_STRINGS.put(s, cached);
}
add(cached);
}
}
private String jsString(String s, boolean useSlashV) {
int singleq = 0, doubleq = 0;
// could count the quotes and pick the optimal quote character
for (int i = 0; i < s.length(); i++) {
switch (s.charAt(i)) {
case '"': doubleq++; break;
case '\'': singleq++; break;
}
}
String doublequote, singlequote;
char quote;
if (preferSingleQuotes ?
(singleq <= doubleq) : (singleq < doubleq)) {
// more double quotes so enclose in single quotes.
quote = '\'';
doublequote = "\"";
singlequote = "\\\'";
} else {
// more single quotes so escape the doubles
quote = '\"';
doublequote = "\\\"";
singlequote = "\'";
}
return strEscape(s, quote, doublequote, singlequote, "\\\\",
outputCharsetEncoder, useSlashV, false);
}
/** Escapes regular expression */
String regexpEscape(String s, CharsetEncoder outputCharsetEncoder) {
return strEscape(s, '/', "\"", "'", "\\", outputCharsetEncoder, false, true);
}
/**
* Escapes the given string to a double quoted (") JavaScript/JSON string
*/
String escapeToDoubleQuotedJsString(String s) {
return strEscape(s, '"', "\\\"", "\'", "\\\\", null, false, false);
}
/* If the user doesn't want to specify an output charset encoder, assume
they want Latin/ASCII characters only.
*/
String regexpEscape(String s) {
return regexpEscape(s, null);
}
/** Helper to escape JavaScript string as well as regular expression */
private String strEscape(
String s,
char quote,
String doublequoteEscape,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> maps m1
* and m2 is the map of the union of names with {@link JSType#getLeastSupertype}
* to meet the m1 type and m2 type.
*
* @see NodeTraversal
* @see DataFlowAnalysis
*
*/
public class Scope
implements StaticScope<JSType>, StaticSymbolTable<Scope.Var, Scope.Var> {
private final Map<String, Var> vars = new LinkedHashMap<String, Var>();
private final Scope parent;
private final int depth;
private final Node rootNode;
/** Whether this is a bottom scope for the purposes of type inference. */
private final boolean isBottom;
private Var arguments;
private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no declared type
var.getParentNode().isVar() &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var
implements StaticSlot<JSType>, StaticReference<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
/** @see #isMarkedEscaped */
private boolean markedEscaped = false;
/** @see #isMarkedAssignedExactlyOnce */
private boolean markedAssignedExactlyOnce = false;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> int index, CompilerInput input) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
@Override
public String getName() {
return name;
}
/**
* Gets the node for the name of the variable.
*/
@Override
public Node getNode() {
return nameNode;
}
CompilerInput getInput() {
return input;
}
@Override
public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile();
}
@Override
public Var getSymbol() {
return this;
}
@Override
public Var getDeclaration() {
return nameNode == null ? null : this;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope).
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return nameNode != null && NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotated by {@code @define}.
*/
public
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> boolean isDefine() {
JSDocInfo info = getJSDocInfo();
return info != null && info.isDefine();
}
public Node getInitialValue() {
return NodeUtil.getRValueOfLValue(nameNode);
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isTypeInferred()}.
*/
@Override
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
@Override
public JSDocInfo getJSDocInfo() {
return nameNode == null ? null : NodeUtil.getBestJSDocInfo(nameNode);
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Resolve this variable's type.
*/
void resolveType(ErrorReporter errorReporter) {
if (type != null) {
type = type.resolve(errorReporter, scope);
}
}
/**
* Returns whether this variable's type is inferred. To get the variable's
* type, see {@link #getType()}.
*/
@Override
public boolean isTypeInferred() {
return typeInferred;
}
public String getInputName() {
if (input == null)
return "<non-file>";
else
return input.getName();
}
public boolean isNoShadow() {
JSDocInfo info = getJSDocInfo();
return info != null && info.isNoShadow();
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name + "{" + type + "}";
}
/**
* Record that this is escaped by
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> an inner scope.
*
* In other words, it's assigned in an inner scope so that it's much harder
* to make assertions about its value at a given point.
*/
void markEscaped() {
markedEscaped = true;
}
/**
* Whether this is escaped by an inner scope.
* Notice that not all scope creators record this information.
*/
boolean isMarkedEscaped() {
return markedEscaped;
}
/**
* Record that this is assigned exactly once..
*
* In other words, it's assigned in an inner scope so that it's much harder
* to make assertions about its value at a given point.
*/
void markAssignedExactlyOnce() {
markedAssignedExactlyOnce = true;
}
/**
* Whether this is assigned exactly once.
* Notice that not all scope creators record this information.
*/
boolean isMarkedAssignedExactlyOnce() {
return markedAssignedExactlyOnce;
}
}
/**
* A special subclass of Var used to distinguish "arguments" in the current
* scope.
*/
// TODO(johnlenz): Include this the list of Vars for the scope.
public static class Arguments extends Var {
Arguments(Scope scope) {
super(
false, // no inferred
"arguments", // always arguments
null, // no declaration node
// TODO(johnlenz): provide the type of "Arguments".
null, // no type info
scope,
-1, // no variable index
null // input
);
}
@Override public boolean equals(Object other) {
if (!(other instanceof Arguments)) {
return false;
}
Arguments otherVar = (Arguments) other;
return otherVar.scope.getRootNode() == scope.getRootNode();
}
@Override public int hashCode() {
return System.identityHashCode(this);
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
this
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.isBottom = false;
this.depth = parent.depth + 1;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param isBottom Whether this is the bottom of a lattice. Otherwise,
* it must be a global scope.
*/
private Scope(Node rootNode, boolean isBottom) {
this.parent = null;
this.rootNode = rootNode;
this.isBottom = isBottom;
this.depth = 0;
}
static Scope createGlobalScope(Node rootNode) {
return new Scope(rootNode, false);
}
static Scope createLatticeBottom(Node rootNode) {
return new Scope(rootNode, true);
}
/** The depth of the scope. The global scope has depth 0. */
int getDepth() {
return depth;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
@Override
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
@Override
public JSType getTypeOfThis() {
if (isGlobal()) {
return ObjectType.cast(rootNode.getJSType());
}
Preconditions.checkState(rootNode.isFunction());
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType.isFunctionType()) {
return nodeType.toMaybeFunctionType().getTypeOfThis();
} else {
return parent.getTypeOfThis();
}
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
*
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input, true);
}
/**
* Declares a variable.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
* @param inferred Whether this variable's type is inferred (as opposed
* to declared).
*/
Var declare(String name, Node nameNode,
JSType type, CompilerInput input, boolean inferred) {
Preconditions.checkState(name != null && name.length() > 0);
// Make sure that it's declared only once
Preconditions.checkState(vars.get(name) == null);
Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
@Override
public Var getSlot(String name) {
return getVar(name);
}
@Override
public Var getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Get a unique VAR object to represents "arguments" within this scope
*/
public Var getArgumentsVar() {
if (arguments == null) {
arguments = new Arguments(this);
}
return arguments;
}
/**
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name))
return true;
if (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Return an iterable over all of the variables declared in this scope.
*/
Iterable<Var> getVarIterable() {
return vars.values();
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public StaticScope<JSType> getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
return Collections.unmodifiableCollection(vars.values());
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent == null;
}
/**
* Returns whether this is a local scope (i.e. not the global scope).
*/
public boolean isLocal() {
return !isGlobal();
}
/**
* Gets all variables declared with "var" but without declared types attached.
*/
public Iterator<Var> getDeclarativelyUnboundVarsWithoutTypes() {
return Iterators.filter(
getVars(), DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES);
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> boolean isAllType() {
return true;
}
@Override
public boolean matchesStringContext() {
// Be lenient.
return true;
}
@Override
public boolean matchesObjectContext() {
// Be lenient.
return true;
}
@Override
public boolean canBeCalled() {
return false;
}
@Override
public TernaryValue testForEquality(JSType that) {
return UNKNOWN;
}
@Override
String toStringHelper(boolean forAnnotations) {
return "*";
}
@Override
public String getDisplayName() {
return "<Any Type>";
}
@Override
public boolean hasDisplayName() {
return true;
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseAllType();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
return visitor.caseAllType(that);
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName);
if (tweakFunc == null) {
return;
}
if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) {
getOverridesCalls.add(
new TweakFunctionCall(t.getSourceName(), tweakFunc, n));
return;
}
// Ensure the first parameter (the tweak ID) is a string literal.
Node tweakIdNode = n.getFirstChild().getNext();
if (!tweakIdNode.isString()) {
compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR));
return;
}
String tweakId = tweakIdNode.getString();
// Make sure there is a TweakInfo structure for it.
TweakInfo tweakInfo = allTweaks.get(tweakId);
if (tweakInfo == null) {
tweakInfo = new TweakInfo(tweakId);
allTweaks.put(tweakId, tweakInfo);
}
switch (tweakFunc) {
case REGISTER_BOOLEAN:
case REGISTER_NUMBER:
case REGISTER_STRING:
// Ensure the ID contains only valid characters.
if (!ID_MATCHER.matchesAllOf(tweakId)) {
compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR));
}
// Ensure tweaks are registered in the global scope.
if (!t.inGlobalScope()) {
compiler.report(
t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweaks are registered only once.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId));
break;
}
Node tweakDefaultValueNode = tweakIdNode.getNext().getNext();
tweakInfo.addRegisterCall(t.getSourceName(), tweakFunc, n,
tweakDefaultValueNode);
break;
case OVERRIDE_DEFAULT_
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.Node;
/**
* This interface defines how objects capable of creating scopes from the parse
* tree behave.
*
*/
interface ScopeCreator {
/**
* Creates a {@link Scope} object.
*
* @param n the root node (either a FUNCTION node, a SCRIPT node, or a
* synthetic block node whose children are all SCRIPT nodes)
* @param parent the parent Scope object (may be null)
*/
Scope createScope(Node n, Scope parent);
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A builder for FunctionTypes, because FunctionTypes are so
* ridiculously complex. All methods return {@code this} for ease of use.
*
* Right now, this mostly uses JSDocInfo to infer type information about
* functions. In the long term, developers should extend it to use other
* signals by overloading the various "inferXXX" methods. For example, we
* might want to use {@code goog.inherits} calls as a signal for inheritance, or
* {@code return} statements as a signal for return type.
*
* NOTE(nicksantos): Organizationally, this feels like it should be in Rhino.
* But it depends on some coding convention stuff that's really part
* of JSCompiler.
*
* @author nicksantos@google.com (Nick Santos)
* @author pascallouis@google.com (Pascal-Louis Perez)
*/
final class FunctionTypeBuilder {
private final String fnName;
private final AbstractCompiler compiler;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final Node errorRoot;
private final String sourceName;
private final Scope scope;
private FunctionContents contents = UnknownFunctionContents.get();
private JSType returnType = null;
private boolean returnTypeInferred = false;
private List<ObjectType> implementedInterfaces = null;
private List<ObjectType> extendedInterfaces = null;
private ObjectType baseType = null;
private JSType thisType = null;
private boolean isConstructor = false;
private boolean makesStructs = false;
private boolean makesDicts = false;
private boolean isInterface = false;
private Node parametersNode = null;
private ImmutableList<String> templateTypeNames = ImmutableList.of();
static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning(
"JSC_EXTENDS_WITHOUT_TYPEDEF",
"@extends used without @constructor or @interface for {0}");
static final DiagnosticType EXTENDS_
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportWarning(EXTENDS_NON_OBJECT, fnName, type.toString());
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
}
} else {
return true;
}
}
}
private class ImplementedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportError(BAD_IMPLEMENTED_TYPE, fnName);
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
}
} else {
return true;
}
}
}
/**
* @param fnName The function name.
* @param compiler The compiler.
* @param errorRoot The node to associate with any warning generated by
* this builder.
* @param sourceName A source name for associating any warnings that
* we have to emit.
* @param scope The syntactic scope.
*/
FunctionTypeBuilder(String fnName, AbstractCompiler compiler,
Node errorRoot, String sourceName, Scope scope) {
Preconditions.checkNotNull(errorRoot);
this.fnName = fnName == null ? "" : fnName;
this.codingConvention = compiler.getCodingConvention();
this.typeRegistry = compiler.getTypeRegistry();
this.errorRoot = errorRoot;
this.sourceName = sourceName;
this.compiler = compiler;
this.scope = scope;
}
/**
* Sets the contents of this function.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>,
codingConvention.isOptionalParameter(currentParam) ||
oldParamsListHitOptArgs,
codingConvention.isVarArgsParameter(currentParam));
}
}
// Clone any remaining params that aren't in the function literal,
// but make them optional.
while (oldParams.hasNext()) {
paramBuilder.newOptionalParameterFromNode(oldParams.next());
}
parametersNode = paramBuilder.build();
}
return this;
}
/**
* Infer the return type from JSDocInfo.
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
if (info != null && info.hasReturnType()) {
returnType = info.getReturnType().evaluate(scope, typeRegistry);
returnTypeInferred = false;
}
return this;
}
/**
* Infer the role of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
if (info != null) {
isConstructor = info.isConstructor();
makesStructs = info.makesStructs();
makesDicts = info.makesDicts();
isInterface = info.isInterface();
if (makesStructs && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@struct", fnName);
} else if (makesDicts && !isConstructor) {
reportWarning(CONSTRUCTOR_REQUIRED, "@dict", fnName);
}
// base type
if (info.hasBaseType()) {
if (isConstructor) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
}
}
// Implemented interfaces (for constructors only).
if (info.getImplementedInterfaceCount() > 0) {
if (isConstructor) {
implementedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);
if (maybeInterType !=
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
implementedInterfaces.add((ObjectType) maybeInterType);
}
}
} else if (isInterface) {
reportWarning(
TypeCheck.CONFLICTING_IMPLEMENTED_TYPE, fnName);
} else {
reportWarning(CONSTRUCTOR_REQUIRED, "@implements", fnName);
}
}
// extended interfaces (for interfaces only)
// We've already emitted a warning if this is not an interface.
if (isInterface) {
extendedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getExtendedInterfaces()) {
JSType maybeInterfaceType = t.evaluate(scope, typeRegistry);
if (maybeInterfaceType != null &&
maybeInterfaceType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add((ObjectType) maybeInterfaceType);
}
}
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this if the info is missing.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) {
// Look at the @this annotation first.
inferThisType(info);
if (thisType == null) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info) {
JSType maybeThisType = null;
if (info != null && info.hasThisType()) {
// TODO(johnlenz): In ES5 strict mode a function can have a null or
// undefined "this" value, but all the existing "@this" annotations
// don't declare restricted types.
maybeThisType = info.getThisType().evaluate(scope, typeRegistry)
.restrictByNotNullOrUndefined();
}
if (maybeThisType != null) {
thisType = maybeThisType;
}
return this;
}
/**
* Infer the parameter types from the doc info alone.
*/
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
// Create a fake args parent.
Node lp = IR.paramList();
for (String name : info.getParameterNames()) {
lp.addChildToBack(IR.name(name));
}
return inferParameterTypes(lp, info);
}
/**
* Infer the parameter types from the list of argument names and
* the doc info.
*/
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
@Nullable JSDocInfo info) {
if (argsParent == null) {
if (info == null) {
return this;
} else {
return inferParameterTypes(info);
}
}
// arguments
Node oldParameterType = null;
if (parametersNode != null) {
oldParameterType = parametersNode.getFirstChild();
}
FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
boolean warnedAboutArgList = false;
Set<String> allJsDocParams = (info == null) ?
Sets.<String>newHashSet() :
Sets.newHashSet(info.getParameterNames());
boolean foundTemplateType = false;
boolean isVarArgs = false;
for (Node arg : argsParent.children()) {
String argumentName = arg.getString();
allJsDocParams.remove(argumentName);
// type from JSDocInfo
JSType parameterType = null;
boolean isOptionalParam = isOptionalParameter(arg, info);
isVarArgs = isVarArgsParameter(arg, info);
if (info != null && info.hasParameterType(argumentName)) {
parameterType =
info.getParameterType(argumentName).evaluate(scope, typeRegistry);
} else if (oldParameterType != null &&
oldParameterType.getJSType() != null) {
parameterType = oldParameterType.getJSType();
isOptionalParam = oldParameterType.isOptionalArg();
isVarArgs = oldParameterType.isVarArgs();
} else {
parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
warnedAboutArgList |= addParameter(
builder, parameterType, warnedAboutArgList,
isOptionalParam,
isVarArgs);
if (oldParameterType != null) {
oldParameterType = oldParameterType.getNext();
}
}
// Copy over
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> any old parameters that aren't in the param list.
if (!isVarArgs) {
while (oldParameterType != null && !isVarArgs) {
builder.newParameterFromNode(oldParameterType);
oldParameterType = oldParameterType.getNext();
}
}
for (String inexistentName : allJsDocParams) {
reportWarning(INEXISTANT_PARAM, inexistentName, fnName);
}
parametersNode = builder.build();
return this;
}
/**
* @return Whether the given param is an optional param.
*/
private boolean isOptionalParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isOptionalParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isOptionalArg();
}
/**
* Determine whether this is a var args parameter.
* @return Whether the given param is a var args param.
*/
private boolean isVarArgsParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isVarArgsParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isVarArgs();
}
/**
* Infer the template type from the doc info.
*/
FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info) {
if (info != null) {
templateTypeNames = info.getTemplateTypeNames();
typeRegistry.setTemplateTypeNames(templateTypeNames);
}
return this;
}
/**
* Add a parameter to the param list.
* @param builder A builder.
* @param paramType The parameter type.
* @param warnedAboutArgList Whether we've already warned about arg ordering
* issues (like if optional args appeared before required ones).
* @param isOptional Is this an optional parameter?
* @param isVarArgs Is this a var args parameter?
* @return Whether a warning was emitted.
*/
private boolean addParameter(FunctionParamBuilder builder,
JSType paramType, boolean warnedAboutArgList,
boolean isOptional
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>);
JSType existingType = typeRegistry.getType(fnName);
if (makesStructs) {
fnType.setStruct();
} else if (makesDicts) {
fnType.setDict();
}
if (existingType != null) {
boolean isInstanceObject = existingType.isInstanceType();
if (isInstanceObject || fnName.equals("Function")) {
FunctionType existingFn =
isInstanceObject ?
existingType.toObjectType().getConstructor() :
typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE);
if (existingFn.getSource() == null) {
existingFn.setSource(contents.getSourceNode());
}
if (!existingFn.hasEqualCallType(fnType)) {
reportWarning(TYPE_REDEFINITION, fnName,
fnType.toString(), existingFn.toString());
}
return existingFn;
} else {
// We fall through and return the created type, even though it will fail
// to register. We have no choice as we have to return a function. We
// issue an error elsewhere though, so the user should fix it.
}
}
maybeSetBaseType(fnType);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
return fnType;
}
private void reportWarning(DiagnosticType warning, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, warning, args));
}
private void reportError(DiagnosticType error, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, error, args));
}
/**
* Determines whether the given JsDoc info declares a function type.
*/
static boolean isFunctionTypeDeclaration(JSDocInfo info) {
return info.getParameterCount() > 0 ||
info.hasReturnType() ||
info.hasThisType() ||
info.isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName.substring(0, dotIndex);
Var rootVar = scope.getVar(rootVarName);
if (rootVar != null) {
return rootVar.getScope();
}
}
return scope;
}
/**
* Check whether a type is resolvable in the future
* If this has a supertype that hasn't been resolved yet, then we can assume
* this type will be OK once the super type resolves.
* @param objectType
* @return true if objectType is resolvable in the future
*/
private static boolean hasMoreTagsToResolve(ObjectType objectType) {
Preconditions.checkArgument(objectType.isUnknownType());
if (objectType.getImplicitPrototype() != null) {
// constructor extends class
if (objectType.getImplicitPrototype().isResolved()) {
return false;
} else {
return true;
}
} else {
// interface extends interfaces
FunctionType ctor = objectType.getConstructor();
if (ctor != null) {
for (ObjectType interfaceType : ctor.getExtendedInterfaces()) {
if (!interfaceType.isResolved()) {
return true;
}
}
}
return false;
}
}
/** Holds data dynamically inferred about functions. */
static interface FunctionContents {
/** Returns the source node of this function. May be null. */
Node getSourceNode();
/** Returns if the function may be in externs. */
boolean mayBeFromExterns();
/** Returns if a return of a real value (not undefined) appears. */
boolean mayHaveNonEmptyReturns();
/** Returns if this consists of a single throw. */
boolean mayHaveSingleThrow();
/** Gets a list of variables in this scope that are escaped. */
Iterable<String> getEscapedVarNames();
/** Gets a list of variables whose properties are escaped. */
Set<String> getEscapedQualifiedNames();
/** Gets the number of times each variable has been assigned. */
Multiset<String> getAssignedNameCounts();
}
static class UnknownFunctionContents implements FunctionContents {
private static UnknownFunctionContents singleton =
new UnknownFunctionContents();
static FunctionContents get() {
return singleton;
}
@Override
public
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> /**
* Creates a pass to check global name references at the given warning level.
*/
CheckGlobalNames(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.convention = compiler.getCodingConvention();
this.level = level;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns this for easy chaining.
*/
CheckGlobalNames injectNamespace(GlobalNamespace namespace) {
Preconditions.checkArgument(namespace.hasExternsRoot());
this.namespace = namespace;
return this;
}
@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, externs, root);
}
// Find prototype properties that will affect our analysis.
Preconditions.checkState(namespace.hasExternsRoot());
findPrototypeProps("Object", objectPrototypeProps);
findPrototypeProps("Function", functionPrototypeProps);
objectPrototypeProps.addAll(
convention.getIndirectlyDeclaredProperties());
for (Name name : namespace.getNameForest()) {
// Skip extern names. Externs are often not runnable as real code,
// and will do things like:
// var x;
// x.method;
// which this check forbids.
if (name.inExterns) {
continue;
}
checkDescendantNames(name, name.globalSets + name.localSets > 0);
}
}
private void findPrototypeProps(String type, Set<String> props) {
Name slot = namespace.getSlot(type);
if (slot != null) {
for (Ref ref : slot.getRefs()) {
if (ref.type == Ref.Type.PROTOTYPE_GET) {
Node fullName = ref.getNode().getParent().getParent();
if (fullName.isGetProp()) {
props.add(fullName.getLastChild().getString());
}
}
}
}
}
/**
* Checks to make sure all the descendants of a name are defined if they
* are referenced.
*
* @param name A global name.
* @param nameIsDefined If true, {@code name} is defined. Otherwise, it's
* undefined, and any
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> references to descendant names should emit warnings.
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.getDeclaration();
Name parent = name.parent;
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.getRefs()) {
// Don't worry about global exprs.
boolean isGlobalExpr = ref.getNode().getParent().isExprResult();
if (!isDefined && !isTypedef(ref)) {
if (!isGlobalExpr) {
reportRefToUndefinedName(name, ref);
}
} else if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else {
// Check for late references.
if (ref.scope.isGlobal()) {
// Prototype references are special, because in our reference graph,
// A.prototype counts as a reference to A.
boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET);
Name owner = isPrototypeGet ? name : parent;
boolean singleGlobalParentDecl =
owner != null &&
owner.getDeclaration() != null &&
owner.localSets == 0;
if (singleGlobalParentDecl &&
owner.getDeclaration().preOrderIndex > ref.preOrderIndex
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
} else {
return null;
}
if (type == SubclassType.MIXIN) {
// Only consider mixins that mix two prototypes as related to
// inheritance.
if (!endsWithPrototype(superclass)) {
return null;
}
if (!isDeprecatedCall) {
if (!endsWithPrototype(subclass)) {
return null;
}
// Strip off the prototype from the name.
subclass = subclass.getFirstChild();
}
superclass = superclass.getFirstChild();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
return new SubclassRelationship(type, subclass, superclass);
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.isGetProp()) {
methodName = callName.getLastChild().getString();
} else if (callName.isName()) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
return "superClass_".equals(propertyName) ||
super.isSuperClassReference(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.isGetProp() &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Extracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Extracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.isGetProp()) {
String qualifiedName = callee.getQualifiedName();
if (functionName.equals(qualifiedName)) {
Node target = callee.getNext();
if (target != null && target.isString()) {
className = target.getString();
}
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.isArrayLit()) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.isString()) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return super.identifyTypeDeclarationCall(n);
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount() != 2) {
return super.getSingletonGetterClassName(callNode);
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
super.applySingletonGetter(functionType, getterType, objectType);
functionType.defineDeclaredProperty("getInstance", getterType,
functionType.getSource());
functionType.defineDeclaredProperty("instance_", objectType,
functionType.getSource());
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.isCall());
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName()) ||
super.isPropertyTestFunction(call);
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Node extends Node {
private static final long serialVersionUID = 1L;
NumberNode(double number) {
super(Token.NUMBER);
this.number = number;
}
public NumberNode(double number, int lineno, int charno) {
super(Token.NUMBER, lineno, charno);
this.number = number;
}
@Override
public double getDouble() {
return this.number;
}
@Override
public void setDouble(double d) {
this.number = d;
}
@Override
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
boolean equivalent = super.isEquivalentTo(node, compareJsType, recurse);
if (equivalent) {
double thisValue = getDouble();
double thatValue = ((NumberNode) node).getDouble();
if (thisValue == thatValue) {
// detect the difference between 0.0 and -0.0.
return (thisValue != 0.0) || (1/thisValue == 1/thatValue);
}
}
return false;
}
private double number;
}
private static class StringNode extends Node {
private static final long serialVersionUID = 1L;
StringNode(int type, String str) {
super(type);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
StringNode(int type, String str, int lineno, int charno) {
super(type, lineno, charno);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
/**
* returns the string content.
* @return non null.
*/
@Override
public String getString() {
return this.str;
}
/**
* sets the string content.
* @param str the new value. Non null.
*/
@Override
public void setString(String str) {
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
@Override
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
return (super.isEquivalentTo(
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = createProp(propType, value, propListHead);
}
}
PropListItem createProp(int propType, Object value, PropListItem next) {
return new ObjectPropListItem(propType, value, next);
}
PropListItem createProp(int propType, int value, PropListItem next) {
return new IntPropListItem(propType, value, next);
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count++;
}
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count--;
keys[count] = x.getType();
}
Arrays.sort(keys);
return keys;
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first == null || first.getType() != Token.NAME) {
sb.append("<invalid>");
} else {
sb.append(first.getString());
}
} else if (type == Token.NUMBER) {
sb.append(' ');
sb.append(getDouble());
}
if (printSource) {
int lineno = getLineno();
if (lineno != -1) {
sb.append(' ');
sb.append(lineno);
}
}
if (printAnnotations) {
int[] keys = getSortedPropTypes();
for (int i = 0; i < keys.length; i++) {
int type = keys[i];
PropListItem x = lookupProperty(type);
sb.append(" [");
sb.append(propToString(type));
sb.append(": ");
String value;
switch (type) {
default:
value = x.toString();
break;
}
sb.append(value);
sb.append(']');
}
}
if (printType) {
if (jsType != null) {
String jsTypeString = jsType.toString();
if (jsTypeString != null) {
sb.append
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> type == Token.STRING_KEY) {
if (type == Token.STRING_KEY) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
int slashV1 = this.getIntProp(SLASH_V);
int slashV2 = node.getIntProp(SLASH_V);
if (slashV1 != slashV2) {
return false;
}
} else if (type == Token.CALL) {
if (this.getBooleanProp(FREE_CALL) != node.getBooleanProp(FREE_CALL)) {
return false;
}
}
if (recurse) {
Node n, n2;
for (n = first, n2 = node.first;
n != null;
n = n.next, n2 = n2.next) {
if (!n.isEquivalentTo(n2, compareJsType, true)) {
return false;
}
}
}
return true;
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
String name = getString();
return name.isEmpty() ? null : name;
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.check
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
}
public boolean isAnd() {
return this.getType() == Token.AND;
}
public boolean isArrayLit() {
return this.getType() == Token.ARRAYLIT;
}
public boolean isAssign() {
return this.getType() == Token.ASSIGN;
}
public boolean isAssignAdd() {
return this.getType() == Token.ASSIGN_ADD;
}
public boolean isBlock() {
return this.getType() == Token.BLOCK;
}
public boolean isBreak() {
return this.getType() == Token.BREAK;
}
public boolean isCall() {
return this.getType() == Token.CALL;
}
public boolean isCase() {
return this.getType() == Token.CASE;
}
public boolean isCast() {
return this.getType() == Token.CAST;
}
public boolean isCatch() {
return this.getType() == Token.CATCH;
}
public boolean isComma() {
return this.getType() == Token.COMMA;
}
public boolean isContinue() {
return this.getType() == Token.CONTINUE;
}
public boolean isDebugger() {
return this.getType() == Token.DEBUGGER;
}
public boolean isDec() {
return this.getType() == Token.DEC;
}
public boolean isDefaultCase() {
return this.getType() == Token.DEFAULT_CASE;
}
public boolean isDelProp() {
return this.getType() == Token.DELPROP;
}
public boolean isDo() {
return this.getType() == Token.DO;
}
public boolean isEmpty() {
return this.getType() == Token.EMPTY;
}
public boolean isExprResult() {
return this.getType() == Token.EXPR_RESULT;
}
public boolean isFalse() {
return this.getType() == Token.FALSE;
}
public boolean isFor() {
return this.getType() == Token.FOR;
}
public boolean isFunction() {
return this.getType() == Token.FUNCTION;
}
public boolean isGetterDef() {
return this.getType() == Token.GETTER_DEF;
}
public boolean isGetElem() {
return this.getType() == Token.GETELEM;
}
public boolean isGetProp() {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> return this.getType() == Token.GETPROP;
}
public boolean isHook() {
return this.getType() == Token.HOOK;
}
public boolean isIf() {
return this.getType() == Token.IF;
}
public boolean isIn() {
return this.getType() == Token.IN;
}
public boolean isInc() {
return this.getType() == Token.INC;
}
public boolean isInstanceOf() {
return this.getType() == Token.INSTANCEOF;
}
public boolean isLabel() {
return this.getType() == Token.LABEL;
}
public boolean isLabelName() {
return this.getType() == Token.LABEL_NAME;
}
public boolean isName() {
return this.getType() == Token.NAME;
}
public boolean isNE() {
return this.getType() == Token.NE;
}
public boolean isNew() {
return this.getType() == Token.NEW;
}
public boolean isNot() {
return this.getType() == Token.NOT;
}
public boolean isNull() {
return this.getType() == Token.NULL;
}
public boolean isNumber() {
return this.getType() == Token.NUMBER;
}
public boolean isObjectLit() {
return this.getType() == Token.OBJECTLIT;
}
public boolean isOr() {
return this.getType() == Token.OR;
}
public boolean isParamList() {
return this.getType() == Token.PARAM_LIST;
}
public boolean isRegExp() {
return this.getType() == Token.REGEXP;
}
public boolean isReturn() {
return this.getType() == Token.RETURN;
}
public boolean isScript() {
return this.getType() == Token.SCRIPT;
}
public boolean isSetterDef() {
return this.getType() == Token.SETTER_DEF;
}
public boolean isString() {
return this.getType() == Token.STRING;
}
public boolean isStringKey() {
return this.getType() == Token.STRING_KEY;
}
public boolean isSwitch() {
return this.getType() == Token.SWITCH;
}
public boolean isThis() {
return this.getType() == Token.THIS;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
Node cur;
Node previous = null;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.isTry() && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
if (parent == null) {
if (compiler.isIdeMode()) {
// In IDE mode, we expect that the data flow graph may
// not be well-formed.
return;
} else {
throw new IllegalStateException("Cannot find break target.");
}
}
previous = cur;
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
register(t, name);
}
/**
* Records a forward-declared type name. We will not emit errors if this
* type name never resolves to anything.
*/
public void forwardDeclareType(String name) {
forwardDeclaredTypes.add(name);
}
/**
* Whether this is a forward-declared type name.
*/
public boolean isForwardDeclaredType(String name) {
return forwardDeclaredTypes.contains(name);
}
/** Determines whether the given JS package exists. */
public boolean hasNamespace(String name) {
return namespaces.contains(name);
}
/**
* Looks up a type by name.
*
* @param jsTypeName The name string.
* @return the corresponding JSType object or {@code null} it cannot be found
*/
public JSType getType(String jsTypeName) {
// TODO(user): Push every local type name out of namesToTypes so that
// NamedType#resolve is correct.
TemplateType templateType = templateTypes.get(jsTypeName);
if (templateType != null) {
return templateType;
}
return namesToTypes.get(jsTypeName);
}
public JSType getNativeType(JSTypeNative typeId) {
return nativeTypes[typeId.ordinal()];
}
public ObjectType getNativeObjectType(JSTypeNative typeId) {
return (ObjectType) getNativeType(typeId);
}
public FunctionType getNativeFunctionType(JSTypeNative typeId) {
return (FunctionType) getNativeType(typeId);
}
/**
* Looks up a type by name. To allow for forward references to types, an
* unrecognized string has to be bound to a NamedType object that will be
* resolved later.
*
* @param scope A scope for doing type name resolution.
* @param jsTypeName The name string.
* @param sourceName The name of the source file where this reference appears.
* @param lineno The line number of the reference.
* @return a NamedType if the string argument is not one of the known types,
* otherwise the corresponding JSType object.
*/
public JSType getType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
JSType type = getType(jsTypeName);
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> if (type == null) {
// TODO(user): Each instance should support named type creation using
// interning.
NamedType namedType =
new NamedType(this, jsTypeName, sourceName, lineno, charno);
unresolvedNamedTypes.put(scope, namedType);
type = namedType;
}
return type;
}
/**
* Flushes out the current resolved and unresolved Named Types from
* the type registry. This is intended to be used ONLY before a
* compile is run.
*/
public void clearNamedTypes() {
resolvedNamedTypes.clear();
unresolvedNamedTypes.clear();
}
/**
* Resolve all the unresolved types in the given scope.
*/
public void resolveTypesInScope(StaticScope<JSType> scope) {
for (NamedType type : unresolvedNamedTypes.get(scope)) {
type.resolve(reporter, scope);
}
resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope));
if (scope != null && scope.getParentScope() == null) {
// By default, the global "this" type is just an anonymous object.
// If the user has defined a Window type, make the Window the
// implicit prototype of "this".
PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType(
JSTypeNative.GLOBAL_THIS);
JSType windowType = getType("Window");
if (globalThis.isUnknownType()) {
ObjectType windowObjType = ObjectType.cast(windowType);
if (windowObjType != null) {
globalThis.setImplicitPrototype(windowObjType);
} else {
globalThis.setImplicitPrototype(
getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
}
}
/**
* Creates a type representing optional values of the given type.
* @return the union of the type and the void type
*/
public JSType createOptionalType(JSType type) {
if (type instanceof UnknownType || type.isAllType()) {
return type;
} else {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE));
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
JSType baseType, ImmutableList<JSType> templatizedTypes) {
// Only instance object types can currently be templatized; extend this
// logic when more types can be templatized.
if (baseType instanceof InstanceObjectType) {
ObjectType baseObjType = baseType.toObjectType();
return new InstanceObjectType(
this, baseObjType.getConstructor(), baseObjType.isNativeObjectType(),
templatizedTypes);
} else {
throw new IllegalArgumentException(
"Only instance object types can be templatized");
}
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.PARAM_LIST) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope);
if (arg.getType()
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
} else {
// Otherwise, the type
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
position.setItem(typeNode);
position.setHasBrackets(hasLC);
position.setPositionInformation(lineno, startCharno,
endLineno, endCharno);
currentMarker.setType(position);
}
}
/**
* Adds a name declaration to the current marker.
* @deprecated Use #markName(String, StaticSourceFile, int, int)
*/
@Deprecated
public void markName(String name, int lineno, int charno) {
markName(name, null, lineno, charno);
}
/**
* Adds a name declaration to the current marker.
*/
public void markName(String name, StaticSourceFile file,
int lineno, int charno) {
if (currentMarker != null) {
// Record the name as both a SourcePosition<String> and a
// SourcePosition<Node>. The <String> form is deprecated,
// because <Node> is more consistent with how other name
// references are handled (see #markTypeNode)
//
// TODO(nicksantos): Remove all uses of the Name position
// and replace them with the NameNode position.
JSDocInfo.TrimmedStringPosition position =
new JSDocInfo.TrimmedStringPosition();
position.setItem(name);
position.setPositionInformation(lineno, charno,
lineno, charno + name.length());
currentMarker.setName(position);
SourcePosition<Node> nodePos =
new JSDocInfo.NamePosition();
Node node = Node.newString(Token.NAME, name, lineno, charno);
node.setLength(name.length());
node.setStaticSourceFile(file);
nodePos.setItem(node);
nodePos.setPositionInformation(lineno, charno,
lineno, charno + name.length());
currentMarker.setNameNode(nodePos);
}
}
/**
* Records a block-level description.
*
* @return {@code true} if the description was recorded.
*/
public boolean recordBlockDescription(String description) {
populated = true;
return currentInfo.documentBlock(description);
}
/**
* Records a visibility.
*
* @return {@code true} if the visibility was recorded and {@code false}
* if it was already defined
*/
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>ino.jstype.RelationshipVisitor
* @return the value returned by the visitor
*/
abstract <T> T visit(RelationshipVisitor<T> visitor, JSType that);
/**
* Force this type to resolve, even if the registry is in a lazy
* resolving mode.
* @see #resolve
*/
public final JSType forceResolve(ErrorReporter t, StaticScope<JSType> scope) {
ResolveMode oldResolveMode = registry.getResolveMode();
registry.setResolveMode(ResolveMode.IMMEDIATE);
JSType result = resolve(t, scope);
registry.setResolveMode(oldResolveMode);
return result;
}
/**
* Resolve this type in the given scope.
*
* The returned value must be equal to {@code this}, as defined by
* {@link #isEquivalentTo}. It may or may not be the same object. This method
* may modify the internal state of {@code this}, as long as it does
* so in a way that preserves Object equality.
*
* For efficiency, we should only resolve a type once per compilation job.
* For incremental compilations, one compilation job may need the
* artifacts from a previous generation, so we will eventually need
* a generational flag instead of a boolean one.
*/
public final JSType resolve(ErrorReporter t, StaticScope<JSType> scope) {
if (resolved) {
// TODO(nicksantos): Check to see if resolve() looped back on itself.
// Preconditions.checkNotNull(resolveResult);
if (resolveResult == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return resolveResult;
}
resolved = true;
resolveResult = resolveInternal(t, scope);
resolveResult.setResolvedTypeInternal(resolveResult);
return resolveResult;
}
/**
* @see #resolve
*/
abstract JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope);
void setResolvedTypeInternal(JSType type) {
resolveResult = type;
resolved = true;
}
/** Whether the type has been resolved. */
public final boolean isResolved() {
return resolved;
}
/** Clears the resolved field. */
public final void clearResolved() {
resolved = false;
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> resolveResult = null;
}
/**
* A null-safe resolve.
* @see #resolve
*/
static final JSType safeResolve(
JSType type, ErrorReporter t, StaticScope<JSType> scope) {
return type == null ? null : type.resolve(t, scope);
}
/**
* Certain types have constraints on them at resolution-time.
* For example, a type in an {@code @extends} annotation must be an
* object. Clients should inject a validator that emits a warning
* if the type does not validate, and return false.
*/
public boolean setValidator(Predicate<JSType> validator) {
return validator.apply(this);
}
public static class TypePair {
public final JSType typeA;
public final JSType typeB;
public TypePair(JSType typeA, JSType typeB) {
this.typeA = typeA;
this.typeB = typeB;
}
}
/**
* A string representation of this type, suitable for printing
* in warnings.
*/
@Override
public String toString() {
return toStringHelper(false);
}
/**
* A hash code function for diagnosing complicated issues
* around type-identity.
*/
public String toDebugHashCodeString() {
return "{" + hashCode() + "}";
}
/**
* A string representation of this type, suitable for printing
* in type annotations at code generation time.
*/
public final String toAnnotationString() {
return toStringHelper(true);
}
/**
* @param forAnnotations Whether this is for use in code generator
* annotations. Otherwise, it's for warnings.
*/
abstract String toStringHelper(boolean forAnnotations);
/**
* Modify this type so that it matches the specified type.
*
* This is useful for reverse type-inference, where we want to
* infer that an object literal matches its constraint (much like
* how the java compiler does reverse-inference to figure out generics).
*/
public void matchConstraint(JSType constraint) {}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
@Override
public JSType getLeastSupertype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public JSType getGreatestSubtype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public TernaryValue testForEquality(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public <T> T visit(Visitor<T> visitor) {
throw new UnsupportedOperationException();
}
@Override <T> T visit(RelationshipVisitor<T> visitor, JSType that) {
throw new UnsupportedOperationException();
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.TRUE;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
returnType = safeResolve(returnType, t, scope);
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
paramNode.setJSType(paramNode.getJSType().resolve(t, scope));
}
}
return this;
}
boolean hasUnknownParamsOrReturn() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type == null || type.isUnknownType()) {
return true;
}
}
}
return returnType == null || returnType.isUnknownType();
}
@Override
String toStringHelper(boolean forAnnotations) {
return "[ArrowType]";
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return returnType.hasAnyTemplateTypes()
|| hasTemplatedParameterType();
}
private boolean hasTemplatedParameterType() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type != null && type.hasAnyTemplateTypes()) {
return true;
}
}
}
return false;
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
TypedScopeCreator(AbstractCompiler compiler,
CodingConvention codingConvention) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.codingConvention = codingConvention;
this.typeRegistry = compiler.getTypeRegistry();
this.typeParsingErrorReporter = typeRegistry.getErrorReporter();
this.unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
}
/**
* Creates a scope with all types declared. Declares newly discovered types
* and type properties in the type registry.
*/
@Override
public Scope createScope(Node root, Scope parent) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
JSType globalThis =
typeRegistry.getNativeObjectType(JSTypeNative.GLOBAL_THIS);
// Mark the main root, the externs root, and the src root
// with the global this type.
root.setJSType(globalThis);
root.getFirstChild().setJSType(globalThis);
root.getLastChild().setJSType(globalThis);
// Run a first-order analysis over the syntax tree.
(new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults))
.process(root.getFirstChild(), root.getLastChild());
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type.isFunctionType()) {
FunctionType fnType = type.toMaybeFunctionType();
JSType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes,
delegateCallingConventions);
}
return newScope;
}
/**
* Patches a given global scope by removing variables previously declared in
* a script and re-traversing a new version of that script.
*
* @param globalScope The global scope generated by {@code createScope}.
* @param scriptRoot The script that is modified.
*/
void patchGlobalScope(Scope globalScope, Node scriptRoot) {
// Preconditions: This is supposed to be called only on (named) SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but here we only wipe that part off!
// Remove all variables that were previously declared in this scripts.
// First find all vars to remove then remove them because of iterator!
Iterator<Var> varIter = globalScope.getVars();
List<Var>
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> varsToRemove = Lists.newArrayList();
while (varIter.hasNext()) {
Var oldVar = varIter.next();
if (scriptName.equals(oldVar.getInputName())) {
varsToRemove.add(oldVar);
}
}
for (Var var : varsToRemove) {
globalScope.undeclare(var);
globalScope.getTypeOfThis().toObjectType().removeProperty(var.getName());
}
// Now re-traverse the given script.
GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope);
NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder);
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry));
Scope s = Scope.createGlobalScope(root);
declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE);
declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, DATE_FUNCTION_TYPE);
declareNativeFunctionType(s, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// There is no longer a need to special case ActiveXObject
// but this remains here until we can get the extern forks
// cleaned up.
declareNativeValueType(s, "ActiveXObject", FUNCTION_INSTANCE_TYPE);
return s;
}
private void declareNativeFunctionType
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
Node nameNode = null;
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're building.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Object literals with a @lends annotation aren't analyzed until we
* reach the root of the statement they're defined in.
*
* This ensures that if there are any @lends annotations on the object
* literals, the type on the @lends annotation resolves correctly.
*
* For more information, see
* http://code.google.com/p/closure-compiler/issues/detail?id=314
*/
private List<Node> lentObjectLiterals = null;
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(t, n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n);
break;
case Token.VAR:
defineVar(n);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> // Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.OBJECTLIT:
JSDocInfo info = n.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
if (lentObjectLiterals == null) {
lentObjectLiterals = Lists.newArrayList();
}
lentObjectLiterals.add(n);
} else {
defineObjectLiteral(n);
}
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null && info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>EnumType() ?
objLitType.toMaybeEnumType().getElementsType() :
NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType);
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = NodeUtil.getBestLValueName(keyNode);
if (qualifiedName != null) {
boolean inferred = keyType == null;
defineSlot(keyNode, objLit, qualifiedName, keyType, inferred);
} else if (keyType != null) {
setDeferredType(keyNode, keyType);
}
if (keyType != null && objLitType != null && declareOnOwner) {
// Declare this property on its object literal.
boolean isExtern = keyNode.isFromExterns();
objLitType.defineDeclaredProperty(memberName, keyType, keyNode);
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode =
node.isGetProp() ? node.getFirstChild() :
NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() :
null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's OK to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>SDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, JSDocInfo info) {
Node value = name.getFirstChild();
// variable's type
JSType type = getDeclaredType(sourceName, info, name, value);
if (type == null) {
// The variable's type will be inferred.
type = name.isFromExterns() ? unknownType : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
* There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
return false;
}
return scope.isGlobal() || !type.isReturnTypeInferred();
}
/**
* Creates a new function type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - A function literal that needs a type attached to it.
* - An assignment expression with function-type info in the JsDoc.
*
* All parameters are optional, and we will do the best we can to create
* a function type.
*
* This function will always create a function type, so only call it if
* you're sure that's what you want.
*
* @param rValue The function node.
* @param name the function's name
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> assigned to a qualified name.
*/
private FunctionType createFunctionTypeFromNodes(
@Nullable Node rValue,
@Nullable String name,
@Nullable JSDocInfo info,
@Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global ctor aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() != null &&
var.getType().isFunctionType()) {
FunctionType aliasedType = var.getType().toMaybeFunctionType();
if ((aliasedType.isConstructor() || aliasedType.isInterface()) &&
!aliasedType.isNativeObjectType()) {
functionType = aliasedType;
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
}
}
if (functionType == null) {
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.isFunction();
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = type.toMaybeFunctionType();
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
Var ownerVar = null;
String propName = null;
ObjectType ownerType = null;
if (ownerName != null) {
ownerVar = scope.getVar(ownerName);
if (ownerVar != null) {
ownerType
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = ObjectType.cast(ownerVar.getType());
}
if (name != null) {
propName = name.substring(ownerName.length() + 1);
}
}
FunctionType overriddenType = null;
if (ownerType != null && propName != null) {
overriddenType = findOverriddenFunction(ownerType, propName);
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setContents(getFunctionAnalysisResults(fnRoot))
.inferFromOverriddenFunction(overriddenType, parametersNode)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (ownerType != null && ownerType.isFunctionPrototypeType() &&
ownerType.getOwnerFunction().hasInstanceType()) {
builder.inferThisType(
info, ownerType.getOwnerFunction().getInstanceType());
searchedForThisType = true;
} else if (ownerNode != null && ownerNode.isThis()) {
// If 'this' has a type, use that instead.
// This is a hack, necessary because CollectProperties (below)
// doesn't run with the scope that it's building,
// so scope.getTypeOfThis() will be wrong.
JSType injectedThisType = ownerNode.getJSType();
builder.inferThisType(
info,
injectedThisType == null ?
scope.getTypeOfThis() : injectedThisType);
searchedForThisType = true;
}
if (!searchedForThisType) {
builder.inferThisType(info);
}
functionType = builder
.inferParameterTypes(parametersNode, info)
.buildAndRegister();
}
}
// all done
return functionType;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private FunctionType findOverriddenFunction(
ObjectType ownerType, String propName) {
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
return propType.toMaybeFunctionType();
} else {
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
// If it's not, then check to see if it's implemented
// on an implemented interface.
for (ObjectType iface :
ownerType.getCtorImplementedInterfaces()) {
propType = iface.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
return propType.toMaybeFunctionType();
}
}
}
return null;
}
/**
* Creates a new enum type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - An object literal that needs an enum type attached to it.
* - An assignment expression with an enum tag in the JsDoc.
*
* This function will always create an enum type, so only call it if
* you're sure that's what you want.
*
* @param rValue The node of the enum.
* @param name The enum's name
* @param info The {@link JSDocInfo} attached to the enum definition.
* @param lValueNode The node where this function is being
* assigned.
*/
private EnumType createEnumTypeFromNodes(Node rValue, String name,
JSDocInfo info, Node lValueNode) {
Preconditions.checkNotNull(info);
Preconditions.checkState(info.hasEnumParameterType());
EnumType enumType = null;
if (rValue != null && rValue.isQualifiedName()) {
// Handle an aliased enum.
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
if (enumType == null) {
JSType elementsType =
info.getEnumParameterType().evaluate(scope, typeRegistry);
enumType = typeRegistry.createEnumType(name, rValue, elementsType);
if (rValue != null && rValue.isObjectLit()) {
// collect enum elements
Node key = rValue.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (keyName == null) {
// GET and SET don't have a String value;
compiler.report(
JSError.make(sourceName, key
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>, ENUM_NOT_CONSTANT, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName, key);
}
key = key.getNext();
}
}
}
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualified names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
boolean isGlobalVar = n.isName() && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.isVar() ||
parent.isFunction());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.isGetProp() && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// The input may be null if we are working with a AST snippet. So read
// the extern info from the node.
boolean isExtern = n.isFromExterns();
Var newVar = null;
// declared in closest scope?
CompilerInput input = compiler.getInput(inputId);
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
newVar = validator.expectUndeclaredVariable(
sourceName, input, n, parent, oldVar, variableName, type);
} else {
if (type != null) {
setDeferredType(n, type);
}
newVar =
scopeToDeclareIn.declare(variableName,
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> n, type, input, inferred);
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.isObjectLit() ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
}
// We need to do some additional work for constructors and interfaces.
FunctionType fnType = JSType.toMaybeFunctionType(type);
if (fnType != null &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
// We want to make sure that when we declare a new instance type
// (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors (like
// function(new:Array). Checking the constructed type against
// the variable name is a sufficient check for this.
if ((fnType.isConstructor() || fnType.isInterface()) &&
variableName.equals(fnType.getReferenceName())) {
finishConstructorDefinition(n, variableName, fnType, scopeToDeclareIn,
input, newVar);
}
}
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
n);
} else {
globalThis.defineDeclaredProperty(variableName, type, n);
}
}
if (isGlobalVar && "Window".equals(variableName)
&& type != null
&& type.isFunctionType()
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType());
}
}
private void finishConstructorDefinition(
Node n, String variableName, FunctionType fnType,
Scope scopeToDeclareIn, CompilerInput input, Var newVar) {
//
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
Property prototypeSlot = fnType.getSlot("prototype");
// When we declare the function prototype implicitly, we
// want to make sure that the function and its prototype
// are declared at the same node. We also want to make sure
// that the if a symbol has both a Var and a JSType, they have
// the same node.
//
// This consistency is helpful to users of SymbolTable,
// because everything gets declared at the same place.
prototypeSlot.setNode(n);
String prototypeName = variableName + ".prototype";
// There are some rare cases where the prototype will already
// be declared. See TypedScopeCreatorTest#testBogusPrototypeInit.
// Fortunately, other warnings will complain if this happens.
Var prototypeVar = scopeToDeclareIn.getVar(prototypeName);
if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) {
scopeToDeclareIn.undeclare(prototypeVar);
}
scopeToDeclareIn.declare(prototypeName,
n, prototypeSlot.getType(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().isEquivalentTo(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!n.isFromExterns()) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Scope scope = getQnameRootScope(n);
return scope != null && scope.isGlobal();
}
/**
* Return the scope for the name of the given node.
*/
private Scope getQnameRootScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.isName()) {
Var var = scope.getVar(root.getString());
if
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> (var != null) {
return var.getScope();
}
}
return null;
}
/**
* Look for a type declaration on a property assignment
* (in an ASSIGN or an object literal key).
*
* @param info The doc info for this property.
* @param lValue The l-value node.
* @param rValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredType(String sourceName, JSDocInfo info,
Node lValue, @Nullable Node rValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
} else if (rValue != null && rValue.isFunction() &&
shouldUseFunctionLiteralType(
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo != null && rValueInfo.hasType()) {
// If rValue has a type-cast, we use the type in the type-cast.
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType() != null
&& !rValue.getJSType().isUnknownType()) {
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.get
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>JSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
&& firstClause.getString().equals(lValue.getString());
if (namesMatch && secondClause.getJSType() != null
&& !secondClause.getJSType().isUnknownType()) {
return secondClause.getJSType();
}
}
}
}
}
}
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
}
private FunctionType getFunctionType(@Nullable Var v) {
JSType t = v == null ? null : v.getType();
ObjectType o = t == null ? null : t.dereference();
return JSType.toMaybeFunctionType(o);
}
/**
* Look for calls that set a delegate method's calling convention.
*/
private void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
codingConvention.checkForCallingConventionDefiningCalls(n,
delegateCallingConventions);
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(
NodeTraversal t, Node n, Node parent) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
FunctionType superCtor = getFunctionType(
scope.getVar(relationship.superclassName));
FunctionType subCtor = getFunctionType(
scope.getVar(relationship.subclassName));
if (superCtor != null && superCtor.isConstructor() &&
subCtor != null && subCtor.isConstructor()) {
ObjectType superClass = superCtor.getInstanceType();
ObjectType subClass = subCtor.getInstanceType();
// superCtor and subCtor might be structural constructors
// (like {function(new:Object)}) so we need to resolve them back
// to
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>codingConvention.getDelegateSuperclassName()));
if (delegatorObject != null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor = delegateBaseObject.getConstructor();
FunctionType delegateSuperCtor = delegateSuperObject.getConstructor();
if (delegatorCtor != null && delegateBaseCtor != null &&
delegateSuperCtor != null) {
FunctionParamBuilder functionParamBuilder =
new FunctionParamBuilder(typeRegistry);
functionParamBuilder.addRequiredParams(
getNativeType(U2U_CONSTRUCTOR_TYPE));
FunctionType findDelegate = typeRegistry.createFunctionType(
typeRegistry.createDefaultObjectUnion(delegateBaseObject),
functionParamBuilder.build());
FunctionType delegateProxy = typeRegistry.createConstructorType(
delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX,
null, null, null, null);
delegateProxy.setPrototypeBasedOn(delegateBaseObject);
codingConvention.applyDelegateRelationship(
delegateSuperObject, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Precedence of type information on GETPROPs:
// 1) @type annotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
// 4) ASSIGN to something marked @const
// 5) ASSIGN to anything else
//
// 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff
// the function has JsDoc or has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3 + #4
JSType valueType = getDeclaredType(t.getSourceName(), info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #5
valueType = rhsValue.getJSType();
}
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to an object literal,
// then they are responsible for making sure that the object literal's
// implicit prototype is set up appropriately. We just obey
// the @extends tag.
ObjectType qVarType = ObjectType.cast(qVar.getType());
if (qVarType != null &&
rhsValue != null &&
rhsValue.isObjectLit()) {
typeRegistry.resetImplicitPrototype(
rhsValue.getJSType(), qVarType.getImplicitPrototype());
} else if (!qVar.isTypeInferred()) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to some arbitrary expression,
// there's not much we can do. We just ignore the expression,
// and hope they've annotated their code in a way to tell us
// what props are going to be on that prototype.
return;
}
if (qVar.getScope() == scope) {
scope.undeclare(qVar);
}
}
}
if (valueType == null) {
if (parent.isExprResult())
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>
* goog.bar();
* /* Reset goog.bar to a no-op. /
* goog.bar = function() {};
* }
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
// Check if this is in a conditional block.
// Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> */
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final JSType thisType;
CollectProperties(JSType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
!member.isGetProp() ||
!member.getFirstChild().isThis()) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredType(t.getSourceName(), info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.isName() || name.isString()) &&
thisType.toObjectType() != null) {
thisType.toObjectType().defineDeclaredProperty(
name.getString(),
jsType,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild())
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recursive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, unknownType);
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're building.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
for (Multiset.Entry<String> entry :
contents.getAssignedNameCounts().entrySet()) {
Var v = scope.getVar(entry.getElement());
Preconditions.checkState(v.getScope() == scope);
if (entry.getCount() == 1) {
v.markAssignedExactlyOnce();
}
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node iifeArgumentNode = null;
if (NodeUtil.isCallOrNewTarget(functionNode)) {
iifeArgumentNode
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> = functionNode.getNext();
}
Node body = astParameters.getNext();
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
JSType paramType = jsDocParameter == null ?
unknownType : jsDocParameter.getJSType();
boolean inferred = paramType == null || paramType == unknownType;
if (iifeArgumentNode != null && inferred) {
String argumentName = iifeArgumentNode.getQualifiedName();
Var argumentVar =
argumentName == null || scope.getParent() == null
? null : scope.getParent().getVar(argumentName);
if (argumentVar != null && !argumentVar.isTypeInferred()) {
paramType = argumentVar.getType();
}
}
if (paramType == null) {
paramType = unknownType;
}
defineSlot(astParameter, functionNode, paramType, inferred);
if (jsDocParameter != null) {
jsDocParameter = jsDocParameter.getNext();
}
if (iifeArgumentNode != null) {
iifeArgumentNode = iifeArgumentNode.getNext();
}
}
}
}
} // end declareArguments
} // end LocalScopeBuilder
/**
* Does a first-order function analysis that just looks at simple things
* like what variables are escaped, and whether 'this' is used.
*/
private static class FirstOrderFunctionAnalyzer
extends AbstractScopedCallback implements CompilerPass {
private final AbstractCompiler compiler;
private final Map<Node, AstFunctionContents> data;
FirstOrderFunctionAnalyzer(
AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {
this.compiler = compiler;
this.data = outParam;
}
@Override public void process(Node externs, Node root) {
if (externs == null) {
NodeTraversal.traverse(compiler, root, this);
} else {
NodeTraversal.traverseRoots(
compiler, ImmutableList.of(externs, root), this);
}
}
@Override public void enterScope(NodeTraversal
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
data.put(n, new AstFunctionContents(n));
}
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
}
if (t.getScopeDepth() <= 1) {
// The first-order function analyzer looks at two types of variables:
//
// 1) Local variables that are assigned in inner scopes ("escaped vars")
//
// 2) Local variables that are assigned more than once.
//
// We treat all global variables as escaped by default, so there's
// no reason to do this extra computation for them.
return;
}
if (n.isName() && NodeUtil.isLValue(n) &&
// Be careful of bleeding functions, which create variables
// in the inner scope, not the scope where the name appears.
!NodeUtil.isBleedingFunctionName(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordAssignedName(name);
}
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
} else if (n.isGetProp() && n.isUnscopedQualifiedName() &&
NodeUtil.isLValue(n)) {
String name = NodeUtil.getRootOfQualifiedName(n).getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode())
.recordEscapedQualifiedName(n.getQualifiedName());
}
}
}
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.graph.GraphvizGraph;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.rhino.Node;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Pass factories and meta-data for native Compiler passes.
*
* @author nicksantos@google.com (Nick Santos)
*/
public abstract class PassConfig {
// Used by subclasses in this package.
final CompilerOptions options;
/**
* A memoized version of scopeCreator. It must be memoized so that
* we can make two separate passes over the AST, one for inferring types
* and one for checking types.
*/
private MemoizedScopeCreator typedScopeCreator;
/**
* This is the scope creator that {@code TypedScopeCreator} delegates to.
*/
private TypedScopeCreator internalScopeCreator;
/** The global typed scope. */
Scope topScope = null;
public PassConfig(CompilerOptions options) {
this.options = options;
}
/**
* Regenerates the top scope from scratch.
*
* @param compiler The compiler for which the global scope is regenerated.
* @param root The root of the AST.
*/
void regenerateGlobalTypedScope(AbstractCompiler compiler, Node root) {
internalScopeCreator = new TypedScope
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>Creator(compiler);
typedScopeCreator = new MemoizedScopeCreator(internalScopeCreator);
topScope = typedScopeCreator.createScope(root, null);
}
void clearTypedScope() {
internalScopeCreator = null;
typedScopeCreator = null;
topScope = null;
}
/**
* Regenerates the top scope potentially only for a sub-tree of AST and then
* copies information for the old global scope.
*
* @param compiler The compiler for which the global scope is generated.
* @param scriptRoot The root of the AST used to generate global scope.
*/
void patchGlobalTypedScope(AbstractCompiler compiler, Node scriptRoot) {
Preconditions.checkNotNull(internalScopeCreator);
internalScopeCreator.patchGlobalScope(topScope, scriptRoot);
}
/**
* Gets the scope creator for typed scopes.
*/
MemoizedScopeCreator getTypedScopeCreator() {
return typedScopeCreator;
}
/**
* Gets the global scope, with type information.
*/
Scope getTopScope() {
return topScope;
}
/**
* Gets the checking passes to run.
*
* Checking passes revolve around emitting warnings and errors.
* They also may include pre-processor passes needed to do
* error analysis more effectively.
*
* Clients that only want to analyze code (like IDEs) and not emit
* code will only run checks and not optimizations.
*/
abstract protected List<PassFactory> getChecks();
/**
* Gets the optimization passes to run.
*
* Optimization passes revolve around producing smaller and faster code.
* They should always run after checking passes.
*/
abstract protected List<PassFactory> getOptimizations();
/**
* Gets a graph of the passes run. For debugging.
*/
GraphvizGraph getPassGraph() {
LinkedDirectedGraph<String, String> graph =
LinkedDirectedGraph.createWithoutAnnotations();
Iterable<PassFactory> allPasses =
Iterables.concat(getChecks(), getOptimizations());
String lastPass = null;
String loopStart = null;
for (PassFactory pass : allPasses) {
String passName = pass.getName();
int i = 1;
while (graph.hasNode(passName)) {
passName = pass.getName
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>getName().equals(name)) {
return i;
}
}
throw new IllegalArgumentException(
"No factory named '" + name + "' in the factory list");
}
/**
* Find the first pass provider that does not have a delegate.
*/
final PassConfig getBasePassConfig() {
PassConfig current = this;
while (current instanceof PassConfigDelegate) {
current = ((PassConfigDelegate) current).delegate;
}
return current;
}
/**
* Get intermediate state for a running pass config, so it can
* be paused and started again later.
*/
protected abstract State getIntermediateState();
/**
* Set the intermediate state for a pass config, to restart
* a compilation process that had been previously paused.
*/
protected abstract void setIntermediateState(State state);
/**
* An implementation of PassConfig that just proxies all its method calls
* into an inner class.
*/
static class PassConfigDelegate extends PassConfig {
private final PassConfig delegate;
PassConfigDelegate(PassConfig delegate) {
super(delegate.options);
this.delegate = delegate;
}
@Override protected List<PassFactory> getChecks() {
return delegate.getChecks();
}
@Override protected List<PassFactory> getOptimizations() {
return delegate.getOptimizations();
}
@Override MemoizedScopeCreator getTypedScopeCreator() {
return delegate.getTypedScopeCreator();
}
@Override Scope getTopScope() {
return delegate.getTopScope();
}
@Override protected State getIntermediateState() {
return delegate.getIntermediateState();
}
@Override protected void setIntermediateState(State state) {
delegate.setIntermediateState(state);
}
}
/**
* Intermediate state for a running pass configuration.
*/
public static class State implements Serializable {
private static final long serialVersionUID = 1L;
final Map<String, Integer> cssNames;
final Set<String> exportedNames;
final CrossModuleMethodMotion.IdGenerator crossModuleIdGenerator;
final VariableMap variableMap;
final VariableMap propertyMap;
final VariableMap anonymousFunctionNameMap;
final VariableMap stringMap;
final FunctionNames functionNames;
final String idGeneratorMap;
public State(Map<String, Integer> cssNames, Set<String> exported
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS>.cloneTree();
for (int i = 1; i < argsToBind && params.getFirstChild() != null; i++) {
if (params.getFirstChild().isVarArgs()) {
break;
}
params.removeFirstChild();
}
builder.withParamsNode(params);
}
}
return builder.build();
}
/**
* Notice that "call" and "bind" have the same argument signature,
* except that all the arguments of "bind" (except the first)
* are optional.
*/
private FunctionType getCallOrBindSignature(boolean isCall) {
boolean isBind = !isCall;
FunctionBuilder builder = new FunctionBuilder(registry)
.withReturnType(isCall ? getReturnType() : getBindReturnType(-1))
.withTemplateKeys(getTemplateKeys());
Node origParams = getParametersNode();
if (origParams != null) {
Node params = origParams.cloneTree();
Node thisTypeNode = Node.newString(Token.NAME, "thisType");
thisTypeNode.setJSType(
registry.createOptionalNullableType(getTypeOfThis()));
params.addChildToFront(thisTypeNode);
if (isBind) {
// The arguments of bind() are unique in that they are all
// optional but not undefinable.
for (Node current = thisTypeNode.getNext();
current != null; current = current.getNext()) {
current.setOptionalArg(true);
}
} else if (isCall) {
// The first argument of call() is optional iff all the arguments
// are optional. It's sufficient to check the first argument.
Node firstArg = thisTypeNode.getNext();
if (firstArg == null
|| firstArg.isOptionalArg()
|| firstArg.isVarArgs()) {
thisTypeNode.setOptionalArg(true);
}
}
builder.withParamsNode(params);
}
return builder.build();
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, Node propertyNode) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (prototypeSlot != null &&
objType.isEquivalentTo(prototypeSlot.getType()))
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> {
((ObjectType) prototypeSlot.getType()).clearCachedValues();
}
}
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
public List<FunctionType> getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototypeSlot != null || super.hasCachedValues();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
call = (ArrowType) safeResolve(call, t, scope);
if (prototypeSlot != null) {
prototypeSlot.setType(
safeResolve(prototypeSlot.getType(), t, scope));
}
// Warning about typeOfThis if it doesn't resolve to an ObjectType
// is handled further upstream.
//
// TODO(nicksantos): Handle this correctly if we have a UnionType.
//
// TODO(nicksantos): In ES3, the run-time coerces "null" to the global
// activation object. In ES5, it leaves it as null. Just punt on this
// issue for now by coercing out null. This is complicated by the
// fact that when most people write @this {Foo}, they really don't
// mean "nullable Foo". For certain tags (like @extends) we de-nullify
// the name for them.
JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope);
if (maybeTypeOfThis != null) {
maybeTypeOfThis = maybeTypeOfThis.restrictByNotNullOrUndefined();
}
if (maybeTypeOfThis instanceof ObjectType) {
typeOfThis = maybeTypeOfThis;
}
boolean changed = false;
ImmutableList.Builder<ObjectType> resolvedInterfaces =
ImmutableList.builder();
for (ObjectType iface : implementedInterfaces) {
ObjectType resolvedIface = (ObjectType) iface.resolve(t, scope);
resolvedInterfaces.add(resolvedIface);
changed |= (resolvedIface != iface);
}
if (changed) {
implementedInterfaces = resolvedInterfaces.build();
}
if (subTypes != null) {
for (int i = 0; i
Closure, 3
<FILEB>
<CHANGES>
if (c.canInline(t.getScope())) {
<CHANGEE>
<CHANGES>
private boolean canInline(final Scope scope) {
<CHANGEE>
<CHANGES>
case Token.NAME:
Var var = scope.getOwnSlot(input.getString());
if (var!= null
&& var.getParentNode().isCatch()) {
return true;
}
<CHANGEE>
<FILEE>
<FILEB>
}
// Compute the forward reaching definition.
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
// Process the body of the function.
Preconditions.checkState(t.getScopeRoot().isFunction());
cfa.process(null, t.getScopeRoot().getLastChild());
cfg = cfa.getCfg();
reachingDef = new MustBeReachingVariableDef(cfg, t.getScope(), compiler);
reachingDef.analyze();
candidates = Lists.newLinkedList();
// Using the forward reaching definition search to find all the inline
// candidates
new NodeTraversal(compiler, new GatherCandiates()).traverse(
t.getScopeRoot().getLastChild());
// Compute the backward reaching use. The CFG can be reused.
reachingUses = new MaybeReachingVariableUse(cfg, t.getScope(), compiler);
reachingUses.analyze();
for (Candidate c : candidates) {
<CHANGES>
if (c.canInline()) {
<CHANGEE>
c.inlineVariable();
// If definition c has dependencies, then inlining it may have
// introduced new dependencies for our other inlining candidates.
//
// MustBeReachingVariableDef uses this dependency graph in its
// analysis, so some of these candidates may no longer be valid.
// We keep track of when the variable dependency graph changed
// so that we can back off appropriately.
if (!c.defMetadata.depends.isEmpty()) {
inlinedNewDependencies.add(t.getScope().getVar(c.varName));
}
}
private final Definition defMetadata;
// Nodes related to the use.
private final Node use;
private final Node useCfgNode;
// Number of uses of the variable within the CFG node that represented the
// use in the CFG.
private int numUseWithinUseCfgNode;
Candidate(String varName, Definition defMetadata,
Node use, Node useCfgNode) {
Preconditions.checkArgument(use.isName());
this.varName = varName;
this.defMetadata = defMetadata;
this.use = use;
this.useCfgNode = useCfgNode;
}
private Node getDefCfgNode() {
return defMetadata.node;
}
<CHANGES>
private boolean canInline() {
<CHANGEE>
// Cannot inline a parameter.<SCANS> < subTypes.size(); i++) {
subTypes.set(
i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope)));
}
}
return super.resolveInternal(t, scope);
}
@Override
public String toDebugHashCodeString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return super.toDebugHashCodeString();
}
StringBuilder b = new StringBuilder(32);
b.append("function (");
int paramNum = call.parameters.getChildCount();
boolean hasKnownTypeOfThis = !typeOfThis.isUnknownType();
if (hasKnownTypeOfThis) {
b.append("this:");
b.append(getDebugHashCodeStringOf(typeOfThis));
}
if (paramNum > 0) {
if (hasKnownTypeOfThis) {
b.append(", ");
}
Node p = call.parameters.getFirstChild();
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
while (p != null) {
b.append(", ");
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
}
}
b.append(")");
b.append(": ");
b.append(getDebugHashCodeStringOf(call.returnType));
return b.toString();
}
private String getDebugHashCodeStringOf(JSType type) {
if (type == this) {
return "me";
} else {
return type.toDebugHashCodeString();
}
}
/** Create a new constructor with the parameters and return type stripped. */
public FunctionType cloneWithoutArrowType() {
FunctionType result = new FunctionType(
registry, getReferenceName(), source,
registry.createArrowType(null, null), getInstanceType(),
null, true, false);
result.setPrototypeBasedOn(getInstanceType());
return result;
}
@Override
public boolean hasAnyTemplateTypesInternal() {
return !getTemplateKeys().isEmpty()
|| typeOfThis.hasAnyTemplateTypes()
|| call.hasAnyTemplateTypes();
}
}